From 23d41b4e52502c07d595108ebe7aa1fbfc51c2d3 Mon Sep 17 00:00:00 2001 From: Chad <36685920+chadbrokaw@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:02:13 -0700 Subject: [PATCH] DAH-1360: Upgrade Partners to React 18 (#584) * fix: app works, working through tests * More tests * Atom tests * next wave of tests * more tests * add mini-css-extract * Fix rspec tests * Add more time to failing test * fix dep * fix dep * fix webpack * postcss-preset-env * clean up testing * Fix test snapshot * cleanup * JL PR Feedback * e2e issues --- .eslintrc.js | 2 +- .vscode/settings.json | 31 +- Gemfile | 12 +- Gemfile.lock | 25 +- Procfile.development | 2 +- README.md | 183 +- .../application_form/PaperApplicationForm.js | 14 +- .../preferences/PreferenceForm.js | 11 +- app/javascript/components/atoms/Checkbox.js | 8 +- app/javascript/components/atoms/Column.js | 1 + app/javascript/components/atoms/Spinner.js | 2 +- app/javascript/components/atoms/StyledIcon.js | 10 +- .../LeaseUpApplicationsFilterContainer.js | 3 +- .../lease_ups/LeaseUpApplicationsTable.js | 6 +- .../lease_ups/LeaseUpListingsPage.js | 9 +- .../application_page/PreferenceRankCell.js | 16 +- .../components/molecules/ContentSection.js | 2 +- .../components/molecules/ExpandableTable.js | 8 +- .../components/molecules/InlineModal.js | 10 +- .../components/molecules/Loading.js | 2 +- .../components/molecules/MultiSelect.js | 4 +- .../molecules/ShowHideFiltersButton.js | 6 +- .../components/molecules/StatusDropdown.js | 6 +- .../lease_up_sidebar/LeaseUpStatusButtons.js | 2 +- .../molecules/lease_up_sidebar/StatusItem.js | 2 +- .../components/organisms/ConfirmationModal.js | 2 +- .../components/organisms/PageHeader.js | 4 +- .../components/organisms/TabsSection.js | 5 +- .../components/pattern_library/properties.js | 2 +- .../SupplementalApplicationContainer.js | 6 +- .../sections/Lease.js | 1 + .../sections/RentalAssistance.js | 7 +- app/javascript/packs/pattern_library.js | 82 +- app/javascript/packs/react_application.js | 22 +- .../routes/GoogleAnalyticsTracker.js | 27 +- app/javascript/routes/LeaseUpRoutes.js | 30 +- app/javascript/utils/form/final_form/Field.js | 10 +- .../utils/form/final_form/MultiDateField.js | 5 +- .../utils/form/final_form/MultiSelectField.js | 5 +- .../utils/form/final_form/SearchField.js | 1 + app/views/applications/edit.html.slim | 2 +- app/views/applications/flagged/index.slim | 2 +- app/views/applications/flagged/show.slim | 2 +- app/views/applications/index.html.slim | 2 +- app/views/layouts/application.html.slim | 4 +- .../listings/applications/index.html.slim | 2 +- app/views/listings/applications/new.html.slim | 2 +- app/views/listings/index.html.slim | 2 +- app/views/listings/show.html.slim | 2 +- bin/shakapacker | 13 + bin/shakapacker-dev-server | 13 + bin/webpack | 19 - bin/webpack-dev-server | 19 - bin/yarn | 19 +- config/initializers/react_on_rails.rb | 59 + config/shakapacker.yml | 62 + config/webpack/base.js | 26 + config/webpack/development.js | 21 +- config/webpack/envSpecific.js | 13 + config/webpack/environment.js | 17 - config/webpack/production.js | 8 +- config/webpack/test.js | 8 +- config/webpack/webpack.config.js | 25 + config/webpacker.yml | 64 - package.json | 92 +- spec/javascript/components/IndexTable.test.js | 37 +- .../components/IndexTableCell.test.js | 10 +- .../__snapshots__/IndexTable.test.js.snap | 6327 +-- .../__snapshots__/IndexTableCell.test.js.snap | 9 + .../applications/ApplicationEditPage.test.js | 82 +- .../applications/ApplicationNewPage.test.js | 8 +- .../applications/ApplicationPage.test.js | 141 +- .../applications/ApplicationsPage.test.js | 34 +- .../ListingApplicationsPage.test.js | 6 +- .../ApplicationEditPage.test.js.snap | 2160 +- .../ApplicationNewPage.test.js.snap | 825 +- .../ApplicationPage.test.js.snap | 3513 +- .../ApplicationsPage.test.js.snap | 2410 +- .../ListingApplicationsPage.test.js.snap | 2264 +- .../ApplicationDetails.test.js | 22 +- .../ApplicationDetails.test.js.snap | 1418 +- .../PaperApplicationForm.test.js | 683 +- .../FlaggedApplicationsIndexPage.test.js | 6 +- .../FlaggedApplicationsShowPage.test.js | 6 +- .../FlaggedApplicationsIndexPage.test.js.snap | 1514 +- .../FlaggedApplicationsShowPage.test.js.snap | 1766 +- .../components/atoms/Breadcrumbs.test.js | 161 +- .../components/atoms/Button.test.js | 171 +- .../components/atoms/Checkbox.test.js | 42 +- .../components/atoms/Column.test.js | 18 +- .../components/atoms/FormGroup.test.js | 8 +- .../atoms/FormGroupCheckboxGroup.test.js | 24 +- .../components/atoms/StatusPill.test.js | 40 +- .../components/atoms/StyledIcon.test.js | 42 +- .../atoms/__snapshots__/Button.test.js.snap | 245 + .../__snapshots__/StatusPill.test.js.snap | 80 +- .../__snapshots__/StyledIcon.test.js.snap | 50 + ...LeaseUpApplicationsFilterContainer.test.js | 189 +- .../LeaseUpApplicationsFilters.test.js | 127 +- .../lease_ups/LeaseUpApplicationsPage.test.js | 518 +- .../lease_ups/LeaseUpListingsTable.test.js | 6 +- .../LeaseUpApplicationsPage.test.js.snap | 32972 ++-------------- .../LeaseUpListingsTable.test.js.snap | 4207 +- .../application_page/CheckboxCell.test.js | 21 +- .../PreferenceRankCell.test.js | 68 +- .../application_page/StatusCell.test.js | 23 +- .../PreferenceRankCell.test.js.snap | 61 + .../__snapshots__/StatusCell.test.js.snap | 86 + .../components/listing/ListingPage.test.js | 44 +- .../components/listing/ListingsPage.test.js | 10 +- .../__snapshots__/ListingPage.test.js.snap | 1623 +- .../__snapshots__/ListingsPage.test.js.snap | 4421 +-- .../components/molecules/AlertBox.test.js | 12 +- .../components/molecules/AlertNotice.test.js | 15 +- .../components/molecules/AppCard.test.js | 8 +- .../components/molecules/ContentCard.test.js | 10 +- .../molecules/ContentCardGrid.test.js | 7 +- .../molecules/ContentSection.test.js | 6 +- .../components/molecules/Dropdown.test.js | 33 +- .../molecules/ExpandableTable.test.js | 44 +- .../components/molecules/FormGrid.test.js | 21 +- .../components/molecules/InlineModal.test.js | 10 +- .../components/molecules/MultiSelect.test.js | 63 +- .../components/molecules/Popover.test.js | 74 +- .../molecules/ShowHideFiltersButton.test.js | 29 +- .../molecules/StatusDropdown.test.js | 124 +- .../molecules/SubstatusDropdown.test.js | 99 +- .../components/molecules/TabsMenu.test.js | 102 +- .../components/molecules/UnitDropdown.test.js | 72 +- .../__snapshots__/ContentSection.test.js.snap | 60 +- .../__snapshots__/Dropdown.test.js.snap | 92 + .../__snapshots__/FormGrid.test.js.snap | 31 + .../__snapshots__/InlineModal.test.js.snap | 16 +- .../__snapshots__/MultiSelect.test.js.snap | 169 + .../__snapshots__/Popover.test.js.snap | 32 +- .../ShowHideFiltersButton.test.js.snap | 45 + .../__snapshots__/StatusDropdown.test.js.snap | 371 + .../lease_up_sidebar/LeaseUpSidebar.test.js | 100 +- .../LeaseUpSidebarButtons.test.js | 57 +- .../StatusHistoryContainer.test.js | 51 +- .../lease_up_sidebar/StatusItem.test.js | 47 +- .../lease_up_sidebar/StatusItems.test.js | 121 +- .../__snapshots__/LeaseUpSidebar.test.js.snap | 380 +- .../__snapshots__/StatusItem.test.js.snap | 467 +- .../__snapshots__/StatusItems.test.js.snap | 995 +- .../organisms/ConfirmationModal.test.js | 107 +- .../organisms/LeaveConfirmationModal.test.js | 24 +- .../components/organisms/PageHeader.test.js | 103 +- .../components/organisms/SimpleModal.test.js | 58 +- .../organisms/StatusHistoryPopover.test.js | 74 +- .../organisms/StatusModalWrapper.test.js | 188 +- .../components/organisms/TabsSection.test.js | 7 +- .../ConfirmationModal.test.js.snap | 3 + .../LeaveConfirmationModal.test.js.snap | 362 +- .../__snapshots__/PageHeader.test.js.snap | 61 + .../__snapshots__/SimpleModal.test.js.snap | 960 +- .../StatusHistoryPopover.test.js.snap | 596 +- .../__snapshots__/TabsSection.test.js.snap | 137 +- .../SupplementalApplicationPage.test.js | 428 +- .../SupplementalApplicationPage.test.js.snap | 3439 +- .../sections/ConfirmedHousholdIncome.test.js | 84 +- .../sections/ParkingInformationInputs.test.js | 151 +- .../sections/PreferencesTable.test.js | 15 +- .../sections/RentalAssistance.test.js | 273 +- .../ConfirmedHousholdIncome.test.js.snap | 369 +- .../RentalAssistance.test.js.snap | 1342 +- .../preferences/RentBurdenedPanel.test.js | 18 +- .../RentBurdenedPanel.test.js.snap | 142 +- .../status_update.e2e.js | 12 +- spec/javascript/jestsetup.js | 10 +- .../routes/GoogleAnalyticsTracker.test.js | 58 +- spec/javascript/routes/LeaseUpRoutes.test.js | 127 +- spec/javascript/support/puppeteer/consts.js | 4 +- spec/javascript/testUtils/wrapperUtil.js | 66 +- spec/javascript/utils/SetupBrowserAndPage.js | 6 +- spec/support/react_helpers.rb | 2 +- spec/support/vcr_setup.rb | 10 + yarn.lock | 7705 ++-- 178 files changed, 24916 insertions(+), 65370 deletions(-) create mode 100755 bin/shakapacker create mode 100755 bin/shakapacker-dev-server delete mode 100755 bin/webpack delete mode 100755 bin/webpack-dev-server create mode 100644 config/initializers/react_on_rails.rb create mode 100644 config/shakapacker.yml create mode 100644 config/webpack/base.js create mode 100644 config/webpack/envSpecific.js delete mode 100644 config/webpack/environment.js create mode 100644 config/webpack/webpack.config.js delete mode 100644 config/webpacker.yml create mode 100644 spec/javascript/components/__snapshots__/IndexTableCell.test.js.snap create mode 100644 spec/javascript/components/atoms/__snapshots__/Button.test.js.snap create mode 100644 spec/javascript/components/atoms/__snapshots__/StyledIcon.test.js.snap create mode 100644 spec/javascript/components/lease_ups/application_page/__snapshots__/PreferenceRankCell.test.js.snap create mode 100644 spec/javascript/components/lease_ups/application_page/__snapshots__/StatusCell.test.js.snap create mode 100644 spec/javascript/components/molecules/__snapshots__/Dropdown.test.js.snap create mode 100644 spec/javascript/components/molecules/__snapshots__/FormGrid.test.js.snap create mode 100644 spec/javascript/components/molecules/__snapshots__/MultiSelect.test.js.snap create mode 100644 spec/javascript/components/molecules/__snapshots__/ShowHideFiltersButton.test.js.snap create mode 100644 spec/javascript/components/molecules/__snapshots__/StatusDropdown.test.js.snap create mode 100644 spec/javascript/components/organisms/__snapshots__/ConfirmationModal.test.js.snap create mode 100644 spec/javascript/components/organisms/__snapshots__/PageHeader.test.js.snap diff --git a/.eslintrc.js b/.eslintrc.js index 8cee22a41..5584ffadc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -106,7 +106,7 @@ module.exports = { settings: { react: { // Must be updated when package.json react version is bumped - version: '16.9.0' + version: '18.2.0' }, 'import/resolver': { node: { diff --git a/.vscode/settings.json b/.vscode/settings.json index 0fcbff51c..0b2ed0c74 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,15 @@ { - "javascript.validate.enable": true, - "jest.disabledWorkspaceFolders": [ - "spec/javascript/e2e" - ], - "editor.codeActionsOnSave": { - // For ESLint - "source.fixAll.eslint": true, - }, - "eslint.format.enable": true, - "eslint.lintTask.enable": true, - "eslint.packageManager": "yarn", - "jest.runAllTestsFirst": false, - "jest.rootPath": "./", - "autoimport.sourceRoot": "./app/javascript", - "autoimport.semicolon": false, - "autoimport.absolute": true, -} \ No newline at end of file + "javascript.validate.enable": true, + "jest.disabledWorkspaceFolders": ["spec/javascript/e2e"], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "eslint.format.enable": true, + "eslint.lintTask.enable": true, + "eslint.packageManager": "yarn", + "jest.runAllTestsFirst": false, + "jest.rootPath": "./", + "autoimport.sourceRoot": "./app/javascript", + "autoimport.semicolon": false, + "autoimport.absolute": true +} diff --git a/Gemfile b/Gemfile index 20ce2a0fe..7a591b6a8 100644 --- a/Gemfile +++ b/Gemfile @@ -82,31 +82,23 @@ gem "sentry-raven" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem # gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] -# Added at 2017-10-22 23:46:25 -0700 by dkaplan: gem "devise", "~> 4.9.0" -# Added at 2017-10-22 23:48:56 -0700 by dkaplan: gem "omniauth-salesforce", "~> 1.0.5" gem "omniauth-rails_csrf_protection" -# Added at 2017-10-24 00:03:53 -0700 by dkaplan: gem "restforce", "~> 6.2.2" # handy ruby extensions gem 'facets', require: false -# Added at 2017-10-24 08:23:22 -0700 by dkaplan: gem "slim-rails", "~> 3.1" -# Added at 2017-10-25 00:30:33 -0700 by dkaplan: gem "hashie" -# Added at 2017-10-29 23:29:10 -0700 by dkaplan: -gem "webpacker", "~> 5.4.4" +gem "shakapacker", "7.1.0" -# Added at 2017-10-29 23:57:32 -0700 by dkaplan: -gem "webpacker-react", "~> 0.3.2" +gem "react_on_rails", "13.4.0" -# Added at 2017-11-10 09:14:41 -0800 by dkaplan: gem "pg", "~> 1.4.6" gem 'scout_apm' diff --git a/Gemfile.lock b/Gemfile.lock index 87c21eb6a..b01056696 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -95,6 +95,7 @@ GEM execjs coffee-script-source (1.12.2) concurrent-ruby (1.2.2) + connection_pool (2.4.1) crack (0.4.5) rexml crass (1.0.6) @@ -216,7 +217,7 @@ GEM nio4r (~> 2.0) racc (1.6.2) rack (2.2.6.4) - rack-proxy (0.7.6) + rack-proxy (0.7.7) rack rack-test (2.1.0) rack (>= 1.3) @@ -269,6 +270,12 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) + react_on_rails (13.4.0) + addressable + connection_pool + execjs (~> 2.5) + rails (>= 5.2) + rainbow (~> 3.0) regexp_parser (2.7.0) require_all (3.0.0) responders (3.1.0) @@ -336,6 +343,11 @@ GEM sentry-raven (3.1.2) faraday (>= 1.0) sexp_processor (4.16.1) + shakapacker (7.1.0) + activesupport (>= 5.2) + rack-proxy (>= 0.6.1) + railties (>= 5.2) + semantic_range (>= 2.3.0) simplecov (0.17.1) docile (~> 1.1) json (>= 1.8, < 3) @@ -386,13 +398,6 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.4.4) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - webpacker-react (0.3.2) - webpacker websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -434,6 +439,7 @@ DEPENDENCIES rails_12factor rails_best_practices rails_layout + react_on_rails (= 13.4.0) restforce (~> 6.2.2) rspec-rails (~> 4.0.2) rubocop @@ -442,6 +448,7 @@ DEPENDENCIES scout_apm selenium-webdriver (~> 3.142.7) sentry-raven + shakapacker (= 7.1.0) simplecov (~> 0.10, < 0.18) slim-rails (~> 3.1) spring @@ -451,8 +458,6 @@ DEPENDENCIES vcr web-console (>= 3.3.0) webmock - webpacker (~> 5.4.4) - webpacker-react (~> 0.3.2) RUBY VERSION ruby 3.1.3p185 diff --git a/Procfile.development b/Procfile.development index 671a1fb39..6d7357aeb 100644 --- a/Procfile.development +++ b/Procfile.development @@ -1,2 +1,2 @@ # rails: bin/rails server -p 3000 -webpack: bin/webpack-dev-server --hot +webpack: bin/shakapacker-dev-server --hot diff --git a/README.md b/README.md index ddedd5bff..a09a041a6 100644 --- a/README.md +++ b/README.md @@ -10,37 +10,45 @@ Only showing rspec tests for now: Cross-browser testing done with ## Setup -* Use Ruby 3.1.3 (Set the version using [RVM](https://rvm.io/rvm/install) or [rbenv](https://github.com/rbenv/rbenv)) -* Install [Bundler](https://github.com/bundler/bundler) `gem install bundler` -* Use Node v18.12.x (npm v8.19.2) -* Install Yarn (if you have Homebrew you can run `brew install yarn`) -* Run `yarn install` -* Run `bundle install` + +- Use Ruby 3.1.3 (Set the version using [RVM](https://rvm.io/rvm/install) or [rbenv](https://github.com/rbenv/rbenv)) +- Install [Bundler](https://github.com/bundler/bundler) `gem install bundler` +- Use Node v18.12.x (npm v8.19.2) +- Install Yarn (if you have Homebrew you can run `brew install yarn`) +- Run `yarn install` +- Run `bundle install` - see [here](https://stackoverflow.com/a/19850273/260495) if you have issues installing `pg` gem with Postgres.app, you may need to use: `gem install pg -v 0.21.0 -- --with-pg-config=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config` - if you need to run this command make sure you run `bundle install` again following the success of the postgres installation to install the remaining gems + * Run `overcommit --install` * Create a `.env` file in the root directory and ask a team member for access to the local development secrets * Setup your local database by running `bin/rails db:migrate RAILS_ENV=development` ### VSCode setup + We recommend you use VSCode to develop partners. You can use something else, but you're on your own for setting up linting/autocomplete. #### Installing recommended VSCode extensions + Open the partners projects in VSCode, click the extensions tab and filter by recommended extensions, install the extensions under "Workspace recommendations" #### Configuring VSCode and extensions + Necessary configs are defined in [.vscode/settings.json](.vscode/settings.json). you can override those configs or change additional settings by changing the apps user settings (Code -> Preferences -> Settings or using the shortcut `CMD + ,`) -## To run server -* `yarn start` -* Access the app at [http://localhost:3000/](http://localhost:3000/) +## To run server and client concurrently + +- `yarn start` +- Access the app at [http://localhost:3000/](http://localhost:3000/) ## To update CSS from Pattern Library -* Checkout your desired commit in your local copy of the [sf-dahlia-pattern-library](https://github.com/SFDigitalServices/sf-dahlia-pattern-library) -* Run `npm start` in your pattern lib directory -* In a separate tab, change to the partners directory and run `grunt` + +- Checkout your desired commit in your local copy of the [sf-dahlia-pattern-library](https://github.com/SFDigitalServices/sf-dahlia-pattern-library) +- Run `npm start` in your pattern lib directory +- In a separate tab, change to the partners directory and run `grunt` ## Linting + To lint Ruby code run: `rubocop` To lint the React code run: `yarn lint` @@ -48,11 +56,14 @@ To lint the React code run: `yarn lint` To fix any auto-fixable linting errors run: `yarn lint:fix` ## Visual Studio setup + Install the following extensions: + - [EsLint](https://github.com/Microsoft/vscode-eslint) - [Prettier](https://github.com/prettier/prettier-vscode) To automatically fix linting errors on save, add this to your VSCode workspace settings: + ``` "editor.codeActionsOnSave": { // For ESLint @@ -60,7 +71,6 @@ To automatically fix linting errors on save, add this to your VSCode workspace s }, ``` - ## Rails tests ### Running tests @@ -73,16 +83,18 @@ If the Salesforce API changes for a request, or if the data sent to the API for In order to update the cassettes you have to: -* Go to your failing test. -* Locate the instruction that is creating the cassette with `VCR.use_cassette`. -* Remove the cassette specified from `spec/vcr/` +- Go to your failing test. +- Locate the instruction that is creating the cassette with `VCR.use_cassette`. +- Remove the cassette specified from `spec/vcr/` For example, for: + ``` VCR.use_cassette('listings/applications_controller/index') do ``` You have to remove: + ``` spec/vcr/listings/applications_controller/index.yml ``` @@ -107,7 +119,7 @@ _Note: Snapshots should be pushed to the repo_ To view the e2e tests as they're running, set `HEADLESS` to `false` in [this file](https://github.com/SFDigitalServices/sf-dahlia-lap/blob/main/spec/javascript/support/puppeteer/consts.js#L51) -#### Run server (in a terminal window) +#### Run server and client concurrently (in a terminal window) `yarn start` @@ -115,7 +127,6 @@ To view the e2e tests as they're running, set `HEADLESS` to `false` in [this fil `yarn e2e` - ### Running all or individual tests To run all tests (unit and e2e): @@ -126,140 +137,66 @@ To run an individual test: `yarn test:all path/to/test` -### Writing component unit tests -#### General best practices -1. Use [Enzyme](https://enzymejs.github.io/enzyme/docs/api/shallow.html)’s shallow rendering instead of react-test-renderer.create or Enzyme mount for all unit tests (snapshot and otherwise). - * There are some cases where you’ll need to use mount instead of shallow, like when you need to test componentDidMount functionality or something -2. Snapshot tests are fine for very simple components, but for anything more complex we should write actual unit tests instead of (or along with) snapshot tests - -For more information on why shallow rendering is simpler than full rendering, check out the comments on [this pr](https://github.com/SFDigitalServices/sf-dahlia-lap/pull/386). - -#### Shallow vs. Mount explained -Say you have two components `` and ``: -``` -const B = ({ className }) => ( -
-) - -const A = ({}) => ( -
- -
-) -``` - -`mount(
)` would create a snapshot that looks like this: -``` -
-
-
-``` - -`shallow(
)` would create a snapshot that looks like this: -``` -
- -
-``` +### Writing unit tests with React Testing Library -Shallow rendering is preferred because the snapshot is simpler, and it ensures you're actually writing a unit test, not a test that will search the whole tree. - -##### When do you need to use mount rendering? -- When you need to test functionality of `componentDidMount` -- When you actually want to write an end-to-end snapshot test that looks at all of the children - - There are usually better tests to write than this if you have the time -- When it's less confusing than adding a bunch of `.dive().dive()`'s to your tests - - For example, if you wanted to check that react-final-form adds the correct error label to an input, it's usually easier to mount render and find the class, rather than traverse the shallow render tree with a whole bunch of dive()s. - -#### Unit testing components with form or context -Shallow rendering is more complicated when you're using connected components, that are wrapped with useContext or work with [react-final-form](https://final-form.org/docs/react-final-form/getting-started)'s form objects. +#### General best practices -The [wrapperUtil.js](spec/javascript/testUtils/wrapperUtil.js) contains utils to help with shallow rendering components that use context or form. +[React Testing Library (RTL)](https://testing-library.com/docs/queries/about) is a library for testing React components in a way that resembles how your app's users would interact with your app. Instead of dealing with instances of rendered React components, your tests will work with actual DOM nodes rendered in a headless version of Chromium -##### Example: shallow rendering a component that has a form passed in -Say you want to test a component that looks like this: -``` -const ComponentThatUsesForm = ({ form }) => ( -
- - -
-) -``` +RTL encourages you to interact with your components in the same way a user would, rather than testing implementation details like props being passed, internal state, or styling. This means RTL tests tend to be more resilient to changes in your app's code and can catch a wider range of bugs. -You can test it like: -``` -import { withForm } from 'spec/javascript/testUtils/wrapperUtil.js' -import ComponentA from '...' +1. Interact with your components like a user: Use RTL's fireEvent (or userEvent) functions to simulate user interactions like clicking buttons, typing into inputs, and submitting forms. Avoid interacting with your components in ways a user couldn't, like by setting props or state directly. -test('it renders ComponentA', () => { - const application = { id: 'appid', } +2. Query by specific accessible roles and labels: Use RTL's getByRole, getByLabelText, and other similar functions to select elements in your tests. These functions select elements based on their accessible roles and labels, which is how users find and interact with elements. Avoid selecting elements by their tag name, class name, or other implementation details. - const wrapper = withForm(application, (form) => ) + - When trying to query for something, try to be as specific as possible. A button, for example can be queried with `screen.getByRole('button)`, but you could also be more specific and say `screen.getByRole('button, {name: 'click me'})` + - `getBy*` and `queryBy*` operate slightly differently, the former will throw an error is nothing is found, while the latter will simply return undefined if no elements are found. If you are querying for something that you know isn't there, then use `expect(screen.queryBy*(element)).not.toBeInTheDocument()` + - `[get|query]AllBy*` and `[get|query]By*` are also different, with the former returning an array and the other returning only one (and throwing an error if it finds otherwise). - expect(wrapper.find(ComponentA)).toHaveLength(1) -}) -``` +3. Snapshots are a great way to mass verify that a component is being rendered correctly. However, they should be used sparingly because they are both brittle and not a great way to detect something breaking. +4. Tests should not be overly complex. + - If you feel like a test is testing too many different things, or that your test is trying to run a user flow, consider making it an E2E test. + - Don't double test something. For example, if a component uses a button that is itself being tested independently, then there is no need to test it in another component, unless the button is being used in a novel way -##### Example: shallow rendering a component that uses form and context -Say you want to test a component that looks like this: -``` -const ComponentThatUsesFormAndContext = ({ form, store }) => { +#### Debugging - return ( -
- - -
- ) -} +Some things to consider when debugging: -export withContext(ComponentThatUsesFormAndContext) -``` +- Are you missing a context provider that you can mock using jest? +- Should an action or assertion be wrapped in `act`? +- Are you using getBy when you should be using queryBy? +- Is the test inheriting something from a beforeEach or from a parent test? -You can test it like: -``` -import { shallowWithFormAndContext } from 'spec/javascript/testUtils/wrapperUtil.js' -import ComponentA from '...' +The `screen` constant provides a lot of good debugging tools: -test('it renders ComponentA', () => { - // Note that the context object must have an application on it, because form expects an application. - const context = { - application: { id: 'appid', } - } - - const wrapper = shallowWithFormAndContext( - context, - (form) => - ) - - expect(wrapper.find(ComponentA)).toHaveLength(1) -}) -``` - -#### Example files that follow these best practices -- Non-form component: [StatusHistoryContainer.test.js](spec/javascript/components/molecules/lease_up_sidebar/StatusHistoryContainer.test.js) -- Form-component: [RentalAssistance.test.js](spec/javascript/components/supplemental_application/sections/RentalAssistance.test.js) +- `screen.logTestingPlaygroundURL` will output a url that has encoded everything the test was looking at. Copy the url and past it into the browser to see what the test was looking at rendered (without styling). The tool will also help you to find the most effective selector. +- `screen.debug([component])` will render the component in the terminal in a much more pretty way than console.log will. +- If you are having issues with only one test in a large testing suite, use `test.only(...)` to only run that single test. The inverse is also true, where using `test.skip(...)` will skip that specific test ## Scripts ### Release scripts + More documentation for how these scripts are used during a release in the [partners release process doc](https://sfgovdt.jira.com/wiki/spaces/HOUS/pages/1900544029/Partners+Release+processes+template). #### 1. create_release_branch + Command: `yarn create_release_branch` This script will: + - Create a new branch named `release-` - Merge it with the latest main - Open a PR in a browser window #### 2. print_release_info + Command: `yarn print_release_info -u -t ` Instructions for how to get your github access token are printed by running `yarn print_release_info -h` This script will: + - Print release tracker info you can paste into the [Release Tracker doc](https://docs.google.com/spreadsheets/d/1EUvw2ugaFprt8FxlCUa1yWATSn0KKo4FyRfBJWUwE3M/edit#gid=1500049656) - Output a URL that will create a draft release with description, title, tags, and base branch filled in @@ -268,6 +205,7 @@ This script will: You can debug all tests and Rails server in VS code. Go to debug view (⇧+⌘+D on Mac) From the dropdown in left top corner pick what you want to debug: + - Rails server: for running app - Jest: for any javascript test - Rspec: for ruby tests @@ -279,12 +217,15 @@ For tests you can debug a single file or the whole suite. To enter debug click a To debug javascript, run Rails server in the prefered way. Go to browser and open inspector (⌥+⌘+I). Go to Sources tab and press ⌘+P to search for a file that you want to debug eg. PaperApplicationForm. Click line number to set a breakpoint in the place you want to debug. You can also add watch expressions, step into or over lines like in VS code debugger. ## React Hooks + Wanted to post a basic intro to react hooks here as they will make our code more performant and allow us to use more functional components. I have examples below but you can read more on the [React Documentation](https://reactjs.org/docs/hooks-overview.html) ### useState, useEffect, and useRef Hooks + These hooks are all built-in to react by default. `useState` allows functional components to manage state just like a class component but with a streamlined syntax. + ```js // class version class Dropdown extends React.Component { @@ -306,9 +247,11 @@ const Dropdown = () => { } } ``` + These components now have the exact same level of control over the expanded flag but the below function has less overhead when mounting and unmounting. `useEffect` is the hook that allows us to still take advantage of lifecycle events when necessary. + ```js const Dropdown = ({ styles }) => { // The empty array passed as the second param here diff --git a/app/javascript/components/applications/application_form/PaperApplicationForm.js b/app/javascript/components/applications/application_form/PaperApplicationForm.js index b1e788cef..0a2745acf 100644 --- a/app/javascript/components/applications/application_form/PaperApplicationForm.js +++ b/app/javascript/components/applications/application_form/PaperApplicationForm.js @@ -25,9 +25,7 @@ class PaperApplicationForm extends React.Component { state = { loading: false, submittedValues: {}, - submitType: '', - genderSpecifyRequired: false, - orientationOtherRequired: false + submitType: '' } submitShortForm = async (submittedValues) => { @@ -107,10 +105,6 @@ class PaperApplicationForm extends React.Component { errors.demographics.sexual_orientation_other = isOrientationOtherRequired ? 'Sexual Orientation is required' : undefined - this.setState({ - genderSpecifyRequired: isGenderSpecifyRequired, - orientationOtherRequired: isOrientationOtherRequired - }) } return errors } @@ -127,7 +121,7 @@ class PaperApplicationForm extends React.Component { mutators={{ ...arrayMutators }} - render={({ handleSubmit, form, values, visited }) => ( + render={({ handleSubmit, form, values, visited, errors }) => (
@@ -156,8 +150,8 @@ class PaperApplicationForm extends React.Component {
diff --git a/app/javascript/components/applications/application_form/preferences/PreferenceForm.js b/app/javascript/components/applications/application_form/preferences/PreferenceForm.js index 026232e45..1f94aa3c5 100644 --- a/app/javascript/components/applications/application_form/preferences/PreferenceForm.js +++ b/app/javascript/components/applications/application_form/preferences/PreferenceForm.js @@ -64,10 +64,12 @@ const PreferenceForm = ({ i, name, form, listingPreferences, fullHousehold }) => ) // Set the hidden recordType DeveloperName value - if (!!selectedPreference && selectedPreference.lottery_preference) { - const prefName = selectedPreference.lottery_preference.name - form.change(`preferences[${i}].recordtype_developername`, recordTypeMap[prefName] || 'Custom') - } + React.useEffect(() => { + if (!!selectedPreference && selectedPreference.lottery_preference) { + const prefName = selectedPreference.lottery_preference.name + form.change(`preferences[${i}].recordtype_developername`, recordTypeMap[prefName] || 'Custom') + } + }, [selectedPreference, form, i]) const householdMembersOptions = buildHouseholdMembersOptions(fullHousehold) return ( @@ -80,6 +82,7 @@ const PreferenceForm = ({ i, name, form, listingPreferences, fullHousehold }) => options={listingPreferencesOptions} id={`select-paper-preference-${i}`} onChange={(event) => clearPreference(form, i, event.target.value)} + dataTestId={`${name}-listing-preference-id`} /> type='checkbox' checked={indeterminate || checked} onClick={onClick} + aria-checked={indeterminate ? 'mixed' : checked ? 'true' : 'false'} // onChange handler required because we're setting 'checked' prop, // but we actually want our handler to be onClick instead, // so this is a no-op function. onChange={() => {}} className={classNames('no-margin', { indeterminate: indeterminate })} /> -
{ return ( -
+
diff --git a/app/javascript/components/atoms/StyledIcon.js b/app/javascript/components/atoms/StyledIcon.js index 79e76887e..1ad8a72b4 100644 --- a/app/javascript/components/atoms/StyledIcon.js +++ b/app/javascript/components/atoms/StyledIcon.js @@ -40,14 +40,20 @@ const getBasicSizeStyle = (size) => { } } -const StyledIcon = ({ size = 'default', customSizeRem = null, customFill = null, icon }) => { +const StyledIcon = ({ + size = 'default', + customSizeRem = null, + customFill = null, + icon, + dataTestId +}) => { const fillStyle = customFill && { fill: customFill } const style = { ...(customSizeRem ? getCustomSizeStyle(customSizeRem) : getBasicSizeStyle(size)), ...fillStyle } return ( - + ) diff --git a/app/javascript/components/lease_ups/LeaseUpApplicationsFilterContainer.js b/app/javascript/components/lease_ups/LeaseUpApplicationsFilterContainer.js index 587fc9263..a9d04796b 100644 --- a/app/javascript/components/lease_ups/LeaseUpApplicationsFilterContainer.js +++ b/app/javascript/components/lease_ups/LeaseUpApplicationsFilterContainer.js @@ -94,7 +94,7 @@ const LeaseUpApplicationsFilterContainer = ({ initialValues={appliedFilters} render={({ form, handleSubmit }) => (
-
+
diff --git a/app/javascript/components/lease_ups/LeaseUpApplicationsTable.js b/app/javascript/components/lease_ups/LeaseUpApplicationsTable.js index c24fd9ffd..cde6ec9af 100644 --- a/app/javascript/components/lease_ups/LeaseUpApplicationsTable.js +++ b/app/javascript/components/lease_ups/LeaseUpApplicationsTable.js @@ -2,7 +2,7 @@ import React from 'react' import classNames from 'classnames' import { trim } from 'lodash' -import { Link, useHistory } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import ReactTable from 'react-table' import { @@ -60,13 +60,13 @@ const LeaseUpApplicationsTable = ({ dispatch ] = useAppContext() - const history = useHistory() + const navigate = useNavigate() const updateSelectedApplicationState = (application, navigateToApplication = false) => { applicationRowClicked(dispatch, application) if (navigateToApplication) { - history.push(appPaths.toLeaseUpApplication(application.application_id)) + navigate(appPaths.toLeaseUpApplication(application.application_id)) } } diff --git a/app/javascript/components/lease_ups/LeaseUpListingsPage.js b/app/javascript/components/lease_ups/LeaseUpListingsPage.js index c46a35fa5..d0c5032ad 100644 --- a/app/javascript/components/lease_ups/LeaseUpListingsPage.js +++ b/app/javascript/components/lease_ups/LeaseUpListingsPage.js @@ -1,6 +1,6 @@ import React, { useState } from 'react' -import { withRouter } from 'react-router-dom' +import { useNavigate } from 'react-router-dom' import { listingsPageMounted, listingRowClicked } from 'components/lease_ups/actions/actionCreators' import Loading from 'components/molecules/Loading' @@ -11,9 +11,10 @@ import LeaseUpListingsTable from './LeaseUpListingsTable' import { getLeaseUpListings } from './utils/leaseUpRequestUtils' import TableLayout from '../layouts/TableLayout' -const LeaseUpListingsPage = ({ history }) => { +const LeaseUpListingsPage = () => { const [loading, setLoading] = useState(true) const [listings, setListings] = useState(true) + const navigate = useNavigate() const [, dispatch] = useAppContext() @@ -35,7 +36,7 @@ const LeaseUpListingsPage = ({ history }) => { const onCellClick = ({ original: listing }) => { listingRowClicked(dispatch, listing) - history.push(appPaths.toLeaseUpApplications(listing.id)) + navigate(appPaths.toLeaseUpApplications(listing.id)) } return ( @@ -49,4 +50,4 @@ const LeaseUpListingsPage = ({ history }) => { ) } -export default withRouter(LeaseUpListingsPage) +export default LeaseUpListingsPage diff --git a/app/javascript/components/lease_ups/application_page/PreferenceRankCell.js b/app/javascript/components/lease_ups/application_page/PreferenceRankCell.js index 464b27151..152e9c83d 100644 --- a/app/javascript/components/lease_ups/application_page/PreferenceRankCell.js +++ b/app/javascript/components/lease_ups/application_page/PreferenceRankCell.js @@ -28,9 +28,21 @@ const PreferenceRankCell = ({ preferenceRank, preferenceValidation }) => { return (
{preferenceRank}
- {showXIcon && } + {showXIcon && ( + + )} {showCheckIcon && ( - + )}
) diff --git a/app/javascript/components/molecules/ContentSection.js b/app/javascript/components/molecules/ContentSection.js index 14694043f..931973321 100644 --- a/app/javascript/components/molecules/ContentSection.js +++ b/app/javascript/components/molecules/ContentSection.js @@ -12,7 +12,7 @@ const ContentSection = ({ title, description, children }) => ( ContentSection.Header = ({ title, description }) => ( <> {title &&

{title}

} - {description &&

{description}

} + {description &&
{description}
} ) diff --git a/app/javascript/components/molecules/ExpandableTable.js b/app/javascript/components/molecules/ExpandableTable.js index dd0d90524..9a1034749 100644 --- a/app/javascript/components/molecules/ExpandableTable.js +++ b/app/javascript/components/molecules/ExpandableTable.js @@ -25,12 +25,18 @@ const ExpandableTableRow = ({ return ( <> - + {cells} {renderExpanderButton(idx, row, original, expanded)} diff --git a/app/javascript/components/molecules/InlineModal.js b/app/javascript/components/molecules/InlineModal.js index db404db88..16b5c6dc6 100644 --- a/app/javascript/components/molecules/InlineModal.js +++ b/app/javascript/components/molecules/InlineModal.js @@ -3,7 +3,13 @@ import React from 'react' import classNames from 'classnames' import PropTypes from 'prop-types' -const InlineModal = ({ whiteBackground = false, children, marginBottom = false, id }) => { +const InlineModal = ({ + whiteBackground = false, + children, + marginBottom = false, + id, + dataTestId +}) => { const classes = [ 'inline-modal', 'padding-left--2x', @@ -13,7 +19,7 @@ const InlineModal = ({ whiteBackground = false, children, marginBottom = false, ] return ( -
+
{children}
) diff --git a/app/javascript/components/molecules/Loading.js b/app/javascript/components/molecules/Loading.js index 7889e8496..c78e2b94d 100644 --- a/app/javascript/components/molecules/Loading.js +++ b/app/javascript/components/molecules/Loading.js @@ -11,7 +11,7 @@ const Loading = ({ const loaderHeightWrapperStyle = loaderViewHeight ? { height: loaderViewHeight } : null const renderChildren = !isLoading || renderChildrenWhileLoading return ( -
+
{isLoading && (
diff --git a/app/javascript/components/molecules/MultiSelect.js b/app/javascript/components/molecules/MultiSelect.js index 9fb639773..dc66f4a1d 100644 --- a/app/javascript/components/molecules/MultiSelect.js +++ b/app/javascript/components/molecules/MultiSelect.js @@ -21,7 +21,8 @@ const MultiSelect = ({ options = [], value = [], disabled = false, - onChange = () => {} + onChange = () => {}, + name = undefined }) => { let selectedValues if (value && value[0] && !value[0].value) { @@ -41,6 +42,7 @@ const MultiSelect = ({ onChange={(values) => onChange(values)} options={options} value={selectedValues} + name={name} />
) diff --git a/app/javascript/components/molecules/ShowHideFiltersButton.js b/app/javascript/components/molecules/ShowHideFiltersButton.js index 755dcb15d..cec4678f9 100644 --- a/app/javascript/components/molecules/ShowHideFiltersButton.js +++ b/app/javascript/components/molecules/ShowHideFiltersButton.js @@ -11,7 +11,7 @@ const getNumFiltersAppliedIcon = (numFiltersApplied) => { numFiltersApplied = 9 } - return + return } const ShowHideFiltersButton = ({ @@ -22,7 +22,9 @@ const ShowHideFiltersButton = ({ const showFiltersCount = numFiltersApplied > 0 return ( - + • +
- - Page - -
- -
- - of - - - 1 - -
- - - + xxx1
- - - + zzz1
- -
- +
- Loading... +
+
+ • +
+
+
+ xxx2 +
+
+ zzz2 +
- -
- - -`; - -exports[`IndexTable should render IndexTable 1`] = ` -
-
-
-
+ class="rt-tr -padRow -odd" + role="row" + > +
+ +   + +
+
+ +   + +
+
+ +   + +
+
- first name +
+ +   + +
+
+ +   + +
+
+ +   + +
-
- last name +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
-
-
-
- • + +   + +
+
+ +   + +
+
+ +   +
-
-
- xxx1 -
-
- zzz1
-
-
- • + +   + +
+
+ +   + +
+
+ +   +
-
-
- xxx2 -
-
- zzz2
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - -
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
-
- -   - -
-
- -   - -
-
- -   - -
+ Previous +
-
-
-
- -   + Page +
+ +
+ of + + 1 -
-
+ - -   - -
-
+ + + + + + + + +
+
+
+ Next +
+
+
+
+ Loading... +
+
+
+ +`; + +exports[`IndexTable should render IndexTable 1`] = ` + +
+
- -   - +
- -   - +
+ first name +
+
- -   - +
+ last name +
+
- -   - -
+
+
+ • +
+
+
+ xxx1 +
+
+ zzz1 +
+
+
+
- -   - +
+
+ • +
+
+
+ xxx2 +
+
+ zzz2 +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
- -   - +
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
+
+
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
+
+
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
+
+
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
+
+
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+
+
+
+
+ +   + +
+
+ +   + +
+
+ +   + +
-
-
- -
-
- + Previous + +
+
- Page - -
- -
- - of - + Page +
+ +
+ of + + 1 + + - 1 + - - +
- - -
-
- + Next + +
-
-
- Loading... +
+ Loading... +
-
+ `; diff --git a/spec/javascript/components/__snapshots__/IndexTableCell.test.js.snap b/spec/javascript/components/__snapshots__/IndexTableCell.test.js.snap new file mode 100644 index 000000000..4129ccfab --- /dev/null +++ b/spec/javascript/components/__snapshots__/IndexTableCell.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IndexTableCell should return null if val is undefined 1`] = ``; + +exports[`IndexTableCell should return string value if cell is not being edited 1`] = ` + + test + +`; diff --git a/spec/javascript/components/applications/ApplicationEditPage.test.js b/spec/javascript/components/applications/ApplicationEditPage.test.js index bf8069983..b8bca0012 100644 --- a/spec/javascript/components/applications/ApplicationEditPage.test.js +++ b/spec/javascript/components/applications/ApplicationEditPage.test.js @@ -1,9 +1,7 @@ import React from 'react' -import { mount } from 'enzyme' +import { render, fireEvent, act, screen } from '@testing-library/react' import { clone } from 'lodash' -import { act } from 'react-dom/test-utils' -import renderer from 'react-test-renderer' import ApplicationEditPage from 'components/applications/ApplicationEditPage' @@ -35,20 +33,15 @@ describe('ApplicationEditPage', () => { }) test('it should save correctly', async () => { - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + render( + + ) + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) const expectedApplication = { ...mockApplication, @@ -65,20 +58,15 @@ describe('ApplicationEditPage', () => { const applicationWithInvalidPrefs = { ...mockApplication } applicationWithInvalidPrefs.preferences[1].application_member.first_name = 'james' - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + render( + + ) + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) expect(mockSubmitApplication.mock.calls).toHaveLength(0) }) @@ -92,27 +80,23 @@ describe('ApplicationEditPage', () => { } } - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + render( + + ) + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) expect(mockSubmitApplication.mock.calls).toHaveLength(0) }) describe('should render', () => { test('successfully', () => { - const wrapper = renderer.create( + const { asFragment } = render( { /> ) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) test('successfully with preferences', () => { @@ -141,7 +125,7 @@ describe('ApplicationEditPage', () => { expect(applicationWithPreferences.preferences).toHaveLength(2) expect(applicationWithPreferences.preferences[0].application_member).toBeTruthy() - const wrapper = renderer.create( + const { asFragment } = render( { /> ) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) }) }) diff --git a/spec/javascript/components/applications/ApplicationNewPage.test.js b/spec/javascript/components/applications/ApplicationNewPage.test.js index 753b9b841..5600b3043 100644 --- a/spec/javascript/components/applications/ApplicationNewPage.test.js +++ b/spec/javascript/components/applications/ApplicationNewPage.test.js @@ -1,6 +1,6 @@ import React from 'react' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import ApplicationNewPage from 'components/applications/ApplicationNewPage' @@ -8,10 +8,8 @@ import listing from '../../fixtures/listing' describe('ApplicationNewPage', () => { test('should render succesfully', () => { - const wrapper = renderer.create( - - ) + const { asFragment } = render() - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) }) diff --git a/spec/javascript/components/applications/ApplicationPage.test.js b/spec/javascript/components/applications/ApplicationPage.test.js index 90b84f7dc..cb84f725e 100644 --- a/spec/javascript/components/applications/ApplicationPage.test.js +++ b/spec/javascript/components/applications/ApplicationPage.test.js @@ -1,16 +1,9 @@ -import { shallow } from 'enzyme' +import { screen } from '@testing-library/react' import { act } from 'react-dom/test-utils' -import ApplicationDetails from 'components/applications/application_details/ApplicationDetails' -import ApplicationPage from 'components/applications/ApplicationPage' -import CardLayout from 'components/layouts/CardLayout' -import Loading from 'components/molecules/Loading' - import application from '../../fixtures/application' import saleApplication from '../../fixtures/sale_application' -import { mountAppWithUrl } from '../../testUtils/wrapperUtil' - -const flaggedAppCardSelector = '#content-card-flagged_applications' +import { renderAppWithUrl } from '../../testUtils/wrapperUtil' const FLAGGED_APP_ID = 'flagged_app_id' const NO_FLAGS_ID = 'no_flags_id' @@ -64,50 +57,25 @@ jest.mock('apiService', () => { const mockApplication = application const mockSaleApplication = saleApplication -const getWrapper = async ({ applicationId, waitForLoadingFinish = false, queryParams = '' }) => { +const renderApp = async ({ applicationId, waitForLoadingFinish = false, queryParams = '' }) => { const url = `/applications/${applicationId}${queryParams}` - let wrapper - // have to wrap in await act so that all useEffectOnMount updates finish. await act(async () => { - wrapper = mountAppWithUrl(url) + renderAppWithUrl(url) }) if (waitForLoadingFinish) { await act(tick) - wrapper.update() } - - return wrapper.find(ApplicationPage) } describe('ApplicationPage', () => { describe('with regular application', () => { const appId = REGULAR_APP_ID describe('before application is loaded', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ applicationId: appId }) - }) - - test('should be loading', () => { - expect(wrapper.find(Loading).prop('isLoading')).toBeTruthy() - }) - - test('renders the correct header title and content', () => { - expect(wrapper.find(CardLayout).props().pageHeader.title).toEqual('Application ') - - const headerContentWrapper = shallow(wrapper.find(CardLayout).props().pageHeader.content) - expect(headerContentWrapper.text()).toEqual('Name of Listing: ') - }) - - test('does not render the tab section', () => { - expect(wrapper.find(CardLayout).props().tabSection).toBeUndefined() - }) - - test('does not render ApplicationDetails', () => { - expect(wrapper.find(ApplicationDetails)).toHaveLength(0) + await renderApp({ applicationId: appId }) }) test('called getShortFormApplication', () => { @@ -117,87 +85,103 @@ describe('ApplicationPage', () => { }) describe('After the application finishes loading', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ applicationId: appId, waitForLoadingFinish: true }) + await renderApp({ applicationId: appId, waitForLoadingFinish: true }) }) test('should no longer be loading', () => { - expect(wrapper.find(Loading).prop('isLoading')).toBeFalsy() + expect(screen.queryByTestId('loading-spinner')).not.toBeInTheDocument() }) test('renders ApplicationDetails', () => { - expect(wrapper.find(ApplicationDetails)).toHaveLength(1) + expect( + screen.getByRole('heading', { + name: /application data/i + }) + ).toBeInTheDocument() }) test('populates the header title and content with application details', () => { - expect(wrapper.find(CardLayout).props().pageHeader.title).toEqual( - 'Application APP-00191270' - ) + expect(screen.getByText('Application APP-00191270')).toBeInTheDocument() - const headerContentWrapper = shallow(wrapper.find(CardLayout).props().pageHeader.content) - expect(headerContentWrapper.text()).toEqual('Name of Listing: Test 5/30') + expect( + screen + .getByRole('link', { + name: /test 5\/30/i + }) + .getAttribute('href') + ).toBe('/listings/a0W0x000000GhJUEA0') }) - test('does not render the tab section', () => { - expect(wrapper.find(CardLayout).props().tabSection).toBeUndefined() - }) - - test('should match snapshot', () => { + test('should match snapshot', async () => { // TODO: Expand test coverage on this page to the point that we do not need this snapshot check. - expect(wrapper).toMatchSnapshot() + const { asFragment } = await act(async () => { + return new Promise((resolve) => { + resolve(renderAppWithUrl(`/applications/${appId}`)) + }) + }) + expect(asFragment()).toMatchSnapshot() }) }) }) describe('with sale application', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ applicationId: SALE_APP_ID, waitForLoadingFinish: true }) + await renderApp({ applicationId: SALE_APP_ID, waitForLoadingFinish: true }) }) test('renders Eligibility section', async () => { - expect(wrapper.find('.content-card_title').at(2).text()).toEqual('Eligibility') + expect( + screen.getByRole('heading', { + name: /application data/i + }) + ).toBeInTheDocument() }) test('renders name of lender', async () => { - expect(wrapper.find('.application-details').childAt(2).text()).toContain('Jason Lockhart') + expect(screen.getByText(/jason lockhart/i)).toBeInTheDocument() }) }) describe('with application that has no flags', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ applicationId: NO_FLAGS_ID, waitForLoadingFinish: true }) + await renderApp({ applicationId: NO_FLAGS_ID, waitForLoadingFinish: true }) }) test('does not render flagged app content card', async () => { - expect(wrapper.exists(flaggedAppCardSelector)).toEqual(false) + expect( + screen.queryByRole('heading', { + name: /flagged applications/i + }) + ).not.toBeInTheDocument() }) }) describe('with flagged application', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ applicationId: FLAGGED_APP_ID, waitForLoadingFinish: true }) + await renderApp({ applicationId: FLAGGED_APP_ID, waitForLoadingFinish: true }) }) test('renders flagged app content card', async () => { - expect(wrapper.exists(flaggedAppCardSelector)).toEqual(true) + expect( + screen.queryByRole('heading', { + name: /flagged applications/i + }) + ).toBeInTheDocument() }) test('renders the correct rule name', () => { - expect(wrapper.find(`${flaggedAppCardSelector} > table > tbody > tr`)).toHaveLength(1) expect( - wrapper.find(`${flaggedAppCardSelector} > table > tbody > tr > td`).first().text() - ).toEqual('Name + DOB') + screen.getByRole('cell', { + name: /name \+ dob/i + }) + ).toBeInTheDocument() }) }) describe('with all query params = null', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ + await renderApp({ applicationId: REGULAR_APP_ID, waitForLoadingFinish: true, queryParams: '' @@ -205,34 +189,32 @@ describe('ApplicationPage', () => { }) test('does not render the tab headers', async () => { - expect(wrapper.text()).not.toContain('Supplemental Information') - expect(wrapper.text()).not.toContain('Short Form Application') + expect(screen.queryByText('Supplemental Information')).not.toBeInTheDocument() + expect(screen.queryByText('Short Form Application')).not.toBeInTheDocument() }) test('does not render add button link', async () => { - expect(wrapper.text()).not.toContain('Add new application') + expect(screen.queryByText('Add new application')).not.toBeInTheDocument() }) }) describe('without lease-up URL', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ + await renderApp({ applicationId: REGULAR_APP_ID, waitForLoadingFinish: true }) }) test('does not render the tab headers', async () => { - expect(wrapper.text()).not.toContain('Supplemental Information') - expect(wrapper.text()).not.toContain('Short Form Application') + expect(screen.queryByText('Supplemental Information')).not.toBeInTheDocument() + expect(screen.queryByText('Short Form Application')).not.toBeInTheDocument() }) }) describe('with showAppBtn query param = false', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ + await renderApp({ applicationId: REGULAR_APP_ID, waitForLoadingFinish: true, queryParams: '?showAddBtn=false' @@ -240,14 +222,13 @@ describe('ApplicationPage', () => { }) test('does not render add button link', async () => { - expect(wrapper.text()).not.toContain('Add new application') + expect(screen.queryByText('Add new application')).not.toBeInTheDocument() }) }) describe('with showAppBtn query param = true', () => { - let wrapper beforeEach(async () => { - wrapper = await getWrapper({ + await renderApp({ applicationId: REGULAR_APP_ID, waitForLoadingFinish: true, queryParams: '?showAddBtn=true' @@ -255,7 +236,7 @@ describe('ApplicationPage', () => { }) test('renders add button link', async () => { - expect(wrapper.text()).toContain('Add new application') + expect(screen.queryByText('Add new application')).toBeInTheDocument() }) }) }) diff --git a/spec/javascript/components/applications/ApplicationsPage.test.js b/spec/javascript/components/applications/ApplicationsPage.test.js index 024e9fa11..8182bed50 100644 --- a/spec/javascript/components/applications/ApplicationsPage.test.js +++ b/spec/javascript/components/applications/ApplicationsPage.test.js @@ -1,11 +1,9 @@ import React from 'react' -import { mount } from 'enzyme' +import { render, screen, act } from '@testing-library/react' import moment from 'moment' -import renderer from 'react-test-renderer' import ApplicationsPage from 'components/applications/ApplicationsPage' -import ApplicationsTableContainer from 'components/applications/ApplicationsTableContainer' import mockApplicationsPage from '../../fixtures/applications_page' import listings from '../../fixtures/listings' @@ -18,26 +16,24 @@ jest.mock('apiService', () => { } }) -// FIXME: extract this out and make it generic -var wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms)) - describe('ApplicationsPage', () => { test('should render successfully', async () => { - const wrapper = renderer.create() - await wait(2000) + const { asFragment } = await act(async () => { + return new Promise((resolve) => { + resolve(render()) + }) + }) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) test('should display data correctly', async () => { - const wrapper = mount() - await wait(2000) - const table = wrapper.find(ApplicationsTableContainer) - table.setState({ loading: false }) - const firstRowData = wrapper.find('div.rt-tr-group div.rt-tr').first() - - expect(firstRowData.text()).toContain(mockApplicationsPage[0].listing.name) - expect(firstRowData.text()).toContain( - moment(mockApplicationsPage[0].listing.lottery_date, '').format('MM/DD/YYYY') - ) + await act(() => render()) + + expect(screen.queryAllByText(/200 buchanan \(alchemy by alta\)/i)).toBeTruthy() + expect( + screen.getAllByText( + moment(mockApplicationsPage[0].listing.lottery_date, '').format('MM/DD/YYYY') + ) + ).toBeTruthy() }) }) diff --git a/spec/javascript/components/applications/ListingApplicationsPage.test.js b/spec/javascript/components/applications/ListingApplicationsPage.test.js index 1c21a4a54..728f1cb04 100644 --- a/spec/javascript/components/applications/ListingApplicationsPage.test.js +++ b/spec/javascript/components/applications/ListingApplicationsPage.test.js @@ -1,6 +1,6 @@ import React from 'react' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import ListingApplicationsPage from 'components/listings/ListingApplicationsPage' @@ -22,7 +22,7 @@ describe('ListingApplicationsPage', () => { } test('should render successfully', () => { - const wrapper = renderer.create() - expect(wrapper.toJSON()).toMatchSnapshot() + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() }) }) diff --git a/spec/javascript/components/applications/__snapshots__/ApplicationEditPage.test.js.snap b/spec/javascript/components/applications/__snapshots__/ApplicationEditPage.test.js.snap index 8f10dd940..c6ee4ed89 100644 --- a/spec/javascript/components/applications/__snapshots__/ApplicationEditPage.test.js.snap +++ b/spec/javascript/components/applications/__snapshots__/ApplicationEditPage.test.js.snap @@ -1,26 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ApplicationEditPage should render successfully 1`] = ` -[ +

Edit Application

Application lottery number: 00191270. For listing: Automated Test Listing (do not modify) @@ -29,57 +29,52 @@ exports[`ApplicationEditPage should render successfully 1`] = `

-
, +
@@ -218,81 +204,71 @@ exports[`ApplicationEditPage should render successfully 1`] = `
@@ -601,117 +540,105 @@ exports[`ApplicationEditPage should render successfully 1`] = `

Mailing Address

@@ -722,94 +649,85 @@ exports[`ApplicationEditPage should render successfully 1`] = `

Alternate Contact

@@ -818,29 +736,25 @@ exports[`ApplicationEditPage should render successfully 1`] = `
@@ -916,81 +824,71 @@ exports[`ApplicationEditPage should render successfully 1`] = `
@@ -1139,10 +1025,10 @@ exports[`ApplicationEditPage should render successfully 1`] = `

Household Members @@ -1151,167 +1037,146 @@ exports[`ApplicationEditPage should render successfully 1`] = `

@@ -1459,29 +1306,25 @@ exports[`ApplicationEditPage should render successfully 1`] = `

Preferences

Declared Household Income

@@ -2333,40 +2133,38 @@ exports[`ApplicationEditPage should render successfully 1`] = `
-

, -] + + `; exports[`ApplicationEditPage should render successfully with preferences 1`] = ` -[ +

Edit Application

Application lottery number: 00191270. For listing: Automated Test Listing (do not modify) @@ -2447,57 +2241,52 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `

-
, +
@@ -2636,81 +2416,71 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `
@@ -3019,117 +2752,105 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `

Mailing Address

@@ -3140,94 +2861,85 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `

Alternate Contact

@@ -3236,29 +2948,25 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `
@@ -3334,81 +3036,71 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `
@@ -3557,10 +3237,10 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `

Household Members @@ -3569,167 +3249,146 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `

@@ -3877,29 +3518,25 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `

Preferences

Declared Household Income

@@ -4751,40 +4345,38 @@ exports[`ApplicationEditPage should render successfully with preferences 1`] = `
-

, -] + + `; diff --git a/spec/javascript/components/applications/__snapshots__/ApplicationNewPage.test.js.snap b/spec/javascript/components/applications/__snapshots__/ApplicationNewPage.test.js.snap index 36dd4d0b8..7ad6a2821 100644 --- a/spec/javascript/components/applications/__snapshots__/ApplicationNewPage.test.js.snap +++ b/spec/javascript/components/applications/__snapshots__/ApplicationNewPage.test.js.snap @@ -1,77 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ApplicationNewPage should render succesfully 1`] = ` -[ +

New Application: Automated Test Listing (do not modify)

-
, +
@@ -210,80 +197,71 @@ exports[`ApplicationNewPage should render succesfully 1`] = `
@@ -591,117 +533,105 @@ exports[`ApplicationNewPage should render succesfully 1`] = `

Mailing Address

@@ -712,94 +642,85 @@ exports[`ApplicationNewPage should render succesfully 1`] = `

Alternate Contact

@@ -808,28 +729,25 @@ exports[`ApplicationNewPage should render succesfully 1`] = `
@@ -905,80 +817,71 @@ exports[`ApplicationNewPage should render succesfully 1`] = `
@@ -1127,28 +1018,27 @@ exports[`ApplicationNewPage should render succesfully 1`] = `

Household Members

Reserved and Priority Qualifying Information @@ -1174,40 +1064,37 @@ exports[`ApplicationNewPage should render succesfully 1`] = `

Preferences

Declared Household Income

@@ -1721,40 +1583,37 @@ exports[`ApplicationNewPage should render succesfully 1`] = `
-
, -] + + `; diff --git a/spec/javascript/components/applications/__snapshots__/ApplicationPage.test.js.snap b/spec/javascript/components/applications/__snapshots__/ApplicationPage.test.js.snap index 23025d50b..5ec38986c 100644 --- a/spec/javascript/components/applications/__snapshots__/ApplicationPage.test.js.snap +++ b/spec/javascript/components/applications/__snapshots__/ApplicationPage.test.js.snap @@ -1,2780 +1,809 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ApplicationPage with regular application After the application finishes loading should match snapshot 1`] = ` - - - Name of Listing: - - - Test 5/30 - - , - "title": "Application APP-00191270", - } - } + +
- - Name of Listing: - - + +
+ +
+ -
- - - -
-
- -
+
    +
  • -
    - - -
    -

    - Application Data -

    -
      -
    • -
      -
      -

      - Lottery Number -

      -

      - 00191270 -

      -
      -
      -

      - Application Submission Type -

      -

      - Electronic -

      -
      -
      -

      - Application Submitted Date -

      -

      - 06/19/2018 -

      -
      -
      -

      - Application Language -

      -

      - English -

      -
      -
      -
    • -
    • -
      -
      -

      - Total Household Size -

      -

      - 2 -

      -
      -
      -

      - Total Monthly Rent -

      -

      - None -

      -
      -
      -

      - Status -

      -

      - Submitted -

      -
      -
      -

      - Referral Source -

      -

      - None -

      -
      -
      -

      - Created By -

      -

      - Prod Vertiba -

      -
      -
      -
    • -
    -
    -
    -
    - +

    + Federic +

    +
    +
    +

    -
    -

    - Primary Applicant -

    -
      -
    • -
      -
      -

      - First Name -

      -

      - karen -

      -
      -
      -

      - Middle Name -

      -

      - elizabeth -

      -
      -
      -

      - Last Name -

      -

      - jones -

      -
      -
      -

      - Date of Birth -

      -

      - 01/01/1950 -

      -
      -
      -

      - Email -

      -

      - eee@eeee.com -

      -
      -
      -
    • -
    • -
      -
      -

      - Phone -

      -

      - 9548883321 -

      -
      -
      -

      - Phone Type -

      -

      - Cell -

      -
      -
      -

      - Second Phone -

      -

      - None -

      -
      -
      -

      - Second Phone Type -

      -

      - None -

      -
      -
      -

      - Residence Address -

      -

      - 123 MAIN ST, SAN FRANCISCO, CA, 94105-1804 -

      -
      -
      -

      - Mailing Address -

      -

      - 123 Mailing St, SF Mailing, CA, 94105-1804 -

      -
      -
      -
    • -
    -
    - - +

    + Daaaa +

    +

    +
    +

    -
    -

    - Alternate Contact -

    -
      -
    • -
      -
      -

      - First Name -

      -

      - Federic -

      -
      -
      -

      - Middle Name -

      -

      - Daaaa -

      -
      -
      -

      - Last Name -

      -

      - dayan -

      -
      -
      -

      - Phone -

      -

      - 9954449943 -

      -
      -
      -

      - Phone Type -

      -

      - Cell -

      -
      -
      -
    • -
    • -
      -
      -

      - Agency Name -

      -

      - None -

      -
      -
      -

      - Email -

      -

      - fede@eee.com -

      -
      -
      -

      - Alternate Contact Type -

      -

      - Friend -

      -
      -
      -

      - Alternate Contact Type Other -

      -

      - None -

      -
      -
      -

      - Mailing Address -

      -

      - 123 Alternate Mailing St, SF Mailing, CA, 94105-1804 -

      -
      -
      -
    • -
    -
    - - +

    + dayan +

    + +
    +

    - -
    -

    - Household Members -

    -

    - - - - - - - - - - - - - - - - - - - - - -
    - Name - - Relationship to Applicant - - Date of Birth - - Street - - City - - State - - Zip Code -
    - diego m maradona - - - 06/11/1976 - - 9 de julio - - buenos aires - - fl - - 33026 -
    -

    - - - +

    + 9954449943 +

    +
+
+

+ Phone Type +

+

+ Cell +

+
+
+ +
  • +
    +
    +

    + Agency Name +

    +

    + None +

    +
    +
    +

    - -
    -

    - Reserved and Priority Qualifying Information -

    -
      -
    • -
      -
      -

      - Has Military Service -

      -

      - None -

      -
      -
      -

      - Has Developmental Disability -

      -

      - None -

      -
      -
      -
    • -
    • -
      -
      -

      - Has ADA Priorities Selected -

      -

      - Vision impairments;Mobility impairments;Hearing impairments -

      -
      -
      -

      - Answered Community Screening -

      -

      - None -

      -
      -
      -
    • -
    -
    -
    - - +

    + fede@eee.com +

    + +
    +

    - -
    -

    - Application Preferences -

    -

    - - - - - - - - - - - - - - - - - - - - - -
    - Receives Preference - - Preference Name - - Lottery Status - - Person who claimed Name - - Type of proof - - Preference Lottery Rank - - Opt Out -
    - true - - Alice Griffith Housing Development Resident - - None - - - - -
    - - Neighborhood Resident Housing Preference (NRHP) - - - - - -
    -

    - - - +

    + Friend +

    +
    +
    - -
  • - +
    + +
    - - - - - +
    +

    + Household Members +

    + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Relationship to Applicant + + Date of Birth + + Street + + City + + State + + Zip Code +
    + diego m maradona + + + 06/11/1976 + + 9 de julio + + buenos aires + + fl + + 33026 +
    +
    +
    +

    + Reserved and Priority Qualifying Information +

    +
      +
    • +
      +
      +

      + Has Military Service +

      +

      + None +

      +
      +
      +

      + Has Developmental Disability +

      +

      + None +

      +
      +
      +
    • +
    • +
      +
      +

      + Has ADA Priorities Selected +

      +

      + Vision impairments;Mobility impairments;Hearing impairments +

      +
      +
      +

      + Answered Community Screening +

      +

      + None +

      +
      +
      +
    • +
    +
    +
    +

    + Application Preferences +

    + + + + + + + + + + + + + + + + + + + + + + +
    + Receives Preference + + Preference Name + + Lottery Status + + Person who claimed Name + + Type of proof + + Preference Lottery Rank + + Opt Out +
    + true + + Alice Griffith Housing Development Resident + + None + + + + +
    + + Neighborhood Resident Housing Preference (NRHP) + + + + + +
    +
    +
    +

    + Declared Household Income +

    +
      +
    • +
      +
      +

      + Annual Income +

      +

      + $110,000.00 +

      +
      +
      +
    • +
    • +
      +
      +

      + Monthly Income +

      +

      + None +

      +
      +
      +

      + Housing Voucher or Subsidy +

      +

      + true +

      +
      +
      +
    • +
    +
    +
    +

    + Attachments +

    + +
    +
    +
    +
    + + `; diff --git a/spec/javascript/components/applications/__snapshots__/ApplicationsPage.test.js.snap b/spec/javascript/components/applications/__snapshots__/ApplicationsPage.test.js.snap index 0a6a0aa30..d28e15eb1 100644 --- a/spec/javascript/components/applications/__snapshots__/ApplicationsPage.test.js.snap +++ b/spec/javascript/components/applications/__snapshots__/ApplicationsPage.test.js.snap @@ -1,57 +1,54 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ApplicationsPage should render successfully 1`] = ` -[ +

    Applications

    -
    , +
    - - of - + of 5
    Loading...
    -
    , -] + +
    `; diff --git a/spec/javascript/components/applications/__snapshots__/ListingApplicationsPage.test.js.snap b/spec/javascript/components/applications/__snapshots__/ListingApplicationsPage.test.js.snap index 513f78382..4ae34ddd3 100644 --- a/spec/javascript/components/applications/__snapshots__/ListingApplicationsPage.test.js.snap +++ b/spec/javascript/components/applications/__snapshots__/ListingApplicationsPage.test.js.snap @@ -1,141 +1,134 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ListingApplicationsPage should render successfully 1`] = ` -[ +

    listing name

    -
    , + , +
    -
    +
    - - of - + of 1
    No rows found
    Loading...
    -
    , -] + + `; diff --git a/spec/javascript/components/applications/application_details/ApplicationDetails.test.js b/spec/javascript/components/applications/application_details/ApplicationDetails.test.js index b354a7d55..528d34cde 100644 --- a/spec/javascript/components/applications/application_details/ApplicationDetails.test.js +++ b/spec/javascript/components/applications/application_details/ApplicationDetails.test.js @@ -1,7 +1,6 @@ import React from 'react' -import { mount } from 'enzyme' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import ApplicationDetails from 'components/applications/application_details/ApplicationDetails' @@ -12,24 +11,25 @@ describe('ApplicationDetails', () => { test('without error and as expected', () => { const fileBaseUrl = 'http://www.someurl.com' - const wrapper = renderer.create( + const { asFragment } = render( ) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) test('file links with both url types', () => { const fileBaseUrl = 'http://www.someurl.com' - const wrapper = mount( + const { getAllByRole } = render( ) - expect(wrapper.find('a').first().getDOMNode().getAttribute('href')).toContain( - 'servlet/servlet.FileDownload' - ) - expect(wrapper.find('a').last().getDOMNode().getAttribute('href')).toContain( - 'sfc/servlet.shepherd/version/download' - ) + + const links = getAllByRole('link') + + expect(links[0].getAttribute('href')).toContain('servlet/servlet.FileDownload') + + const lastLink = links.pop() + expect(lastLink.getAttribute('href')).toContain('sfc/servlet.shepherd/version/download') }) }) }) diff --git a/spec/javascript/components/applications/application_details/__snapshots__/ApplicationDetails.test.js.snap b/spec/javascript/components/applications/application_details/__snapshots__/ApplicationDetails.test.js.snap index 722ed702d..930ba3036 100644 --- a/spec/javascript/components/applications/application_details/__snapshots__/ApplicationDetails.test.js.snap +++ b/spec/javascript/components/applications/application_details/__snapshots__/ApplicationDetails.test.js.snap @@ -1,751 +1,753 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ApplicationDetails should render without error and as expected 1`] = ` -
    +
    -

    - Application Data -

    -
      -
    • -
      -
      -

      - Lottery Number -

      -

      - 00191270 -

      -
      -
      -

      - Application Submission Type -

      -

      - Electronic -

      -
      -
      -

      - Application Submitted Date -

      -

      - 06/19/2018 -

      -
      -
      -

      - Application Language -

      -

      - English -

      -
      -
      -
    • -
    • +
        -
        -

        - Total Household Size -

        -

        - 2 -

        -
        -
        -

        - Total Monthly Rent -

        -

        - None -

        -
        -
        -

        - Status -

        -

        - Submitted -

        -
        -
        -

        - Referral Source -

        -

        - None -

        -
        + class="content-card" + > +
        +

        + Lottery Number +

        +

        + 00191270 +

        +
        +
        +

        + Application Submission Type +

        +

        + Electronic +

        +
        +
        +

        + Application Submitted Date +

        +

        + 06/19/2018 +

        +
        +
        +

        + Application Language +

        +

        + English +

        +
        +
        + +
      • -

        - Created By -

        -

        - Prod Vertiba -

        -
        -
    - - -
    -
    -

    +
    +

    + Total Household Size +

    +

    + 2 +

    +
    +
    +

    + Total Monthly Rent +

    +

    + None +

    +
    +
    +

    + Status +

    +

    + Submitted +

    +
    +
    +

    + Referral Source +

    +

    + None +

    +
    +
    +

    + Created By +

    +

    + Prod Vertiba +

    +
    +

    + + +
    +
    - Primary Applicant - -
      -
    • -
      -
      -

      - First Name -

      -

      - karen -

      -
      -
      -

      - Middle Name -

      -

      - elizabeth -

      -
      -
      -

      - Last Name -

      -

      - jones -

      -
      -
      -

      - Date of Birth -

      -

      - 01/01/1950 -

      -
      -
      -

      - Email -

      -

      - eee@eeee.com -

      -
      -
      -
    • -
    • +
        -
        -

        - Phone -

        -

        - 9548883321 -

        -
        -
        -

        - Phone Type -

        -

        - Cell -

        -
        -
        -

        - Second Phone -

        -

        - None -

        -
        -
        -

        - Second Phone Type -

        -

        - None -

        -
        -
        -

        - Residence Address -

        -

        - 123 MAIN ST, SAN FRANCISCO, CA, 94105-1804 -

        -
        + class="content-card" + > +
        +

        + First Name +

        +

        + karen +

        +
        +
        +

        + Middle Name +

        +

        + elizabeth +

        +
        +
        +

        + Last Name +

        +

        + jones +

        +
        +
        +

        + Date of Birth +

        +

        + 01/01/1950 +

        +
        +
        +

        + Email +

        +

        + eee@eeee.com +

        +
        +
        + +
      • -

        - Mailing Address -

        -

        - 123 Mailing St, SF Mailing, CA, 94105-1804 -

        -
        -
    - - -
    -
    -

    +
    +

    + Phone +

    +

    + 9548883321 +

    +
    +
    +

    + Phone Type +

    +

    + Cell +

    +
    +
    +

    + Second Phone +

    +

    + None +

    +
    +
    +

    + Second Phone Type +

    +

    + None +

    +
    +
    +

    + Residence Address +

    +

    + 123 MAIN ST, SAN FRANCISCO, CA, 94105-1804 +

    +
    +
    +

    + Mailing Address +

    +

    + 123 Mailing St, SF Mailing, CA, 94105-1804 +

    +
    +

    + + +
    +
    - Alternate Contact - -
      -
    • -
      -
      -

      - First Name -

      -

      - Federic -

      -
      -
      -

      - Middle Name -

      -

      - Daaaa -

      -
      -
      -

      - Last Name -

      -

      - dayan -

      -
      -
      -

      - Phone -

      -

      - 9954449943 -

      -
      -
      -

      - Phone Type -

      -

      - Cell -

      -
      -
      -
    • -
    • +
        -
        -

        - Agency Name -

        -

        - None -

        -
        -
        -

        - Email -

        -

        - fede@eee.com -

        -
        -
        -

        - Alternate Contact Type -

        -

        - Friend -

        -
        -
        -

        - Alternate Contact Type Other -

        -

        - None -

        -
        + class="content-card" + > +
        +

        + First Name +

        +

        + Federic +

        +
        +
        +

        + Middle Name +

        +

        + Daaaa +

        +
        +
        +

        + Last Name +

        +

        + dayan +

        +
        +
        +

        + Phone +

        +

        + 9954449943 +

        +
        +
        +

        + Phone Type +

        +

        + Cell +

        +
        +
        + +
      • -

        - Mailing Address -

        -

        - 123 Alternate Mailing St, SF Mailing, CA, 94105-1804 -

        -
        -
    - - -
    -
    -

    - Household Members -

    - - - - - - - - - - - - - - - - - - - - - - -
    - Name - - Relationship to Applicant - - Date of Birth - - Street - - City - - State - - Zip Code -
    - diego m maradona - - - 06/11/1976 - - 9 de julio - - buenos aires - - fl - - 33026 -
    -
    -
    -

    +
    +

    + Agency Name +

    +

    + None +

    +
    +
    +

    + Email +

    +

    + fede@eee.com +

    +
    +
    +

    + Alternate Contact Type +

    +

    + Friend +

    +
    +
    +

    + Alternate Contact Type Other +

    +

    + None +

    +
    +
    +

    + Mailing Address +

    +

    + 123 Alternate Mailing St, SF Mailing, CA, 94105-1804 +

    +
    +

    + + +
    +
    - Reserved and Priority Qualifying Information - -
      + Household Members + + + + + + + + + + + + + + + + + + + + + + + +
      + Name + + Relationship to Applicant + + Date of Birth + + Street + + City + + State + + Zip Code +
      + diego m maradona + + + 06/11/1976 + + 9 de julio + + buenos aires + + fl + + 33026 +
      +
    +
    -
  • -
    -
    -

    - Has Military Service -

    -

    - None -

    -
    -
    -

    - Has Developmental Disability -

    -

    - None -

    -
    -
    -
  • -
  • +
      -
      -

      - Has ADA Priorities Selected -

      -

      - Vision impairments;Mobility impairments;Hearing impairments -

      -
      + class="content-card" + > +
      +

      + Has Military Service +

      +

      + None +

      +
      +
      +

      + Has Developmental Disability +

      +

      + None +

      +
      +
      + +
    • -

      - Answered Community Screening -

      -

      - None -

      -
      -
  • - - -
    -
    -

    +
    +

    + Has ADA Priorities Selected +

    +

    + Vision impairments;Mobility impairments;Hearing impairments +

    +
    +
    +

    + Answered Community Screening +

    +

    + None +

    +
    +

    + + +
    +
    - Application Preferences - - - - - - - - - - - - - - - - - - - - - - - -
    - Receives Preference - - Preference Name - - Lottery Status - - Person who claimed Name - - Type of proof - - Preference Lottery Rank - - Opt Out -
    - true - - Alice Griffith Housing Development Resident - - None - - - - -
    - - Neighborhood Resident Housing Preference (NRHP) - - - - - -
    -
    -
    -

    - Declared Household Income -

    -
      + Application Preferences + + + + + + + + + + + + + + + + + + + + + + + +
      + Receives Preference + + Preference Name + + Lottery Status + + Person who claimed Name + + Type of proof + + Preference Lottery Rank + + Opt Out +
      + true + + Alice Griffith Housing Development Resident + + None + + + + +
      + + Neighborhood Resident Housing Preference (NRHP) + + + + + +
      +
    +
    -
  • -
    -
    -

    - Annual Income -

    -

    - $110,000.00 -

    -
    -
    -
  • -
  • +
      -
      -

      - Monthly Income -

      -

      - None -

      +

      + Annual Income +

      +

      + $110,000.00 +

      +
      + +
    • -

      - Housing Voucher or Subsidy -

      -

      - true -

      -
      -
  • - - -
    -
    -

    +
    +

    + Monthly Income +

    +

    + None +

    +
    +
    +

    + Housing Voucher or Subsidy +

    +

    + true +

    +
    +

    + + +
    +
    - Attachments - - +

    + Attachments +

    + +
    - + `; diff --git a/spec/javascript/components/applications/application_form/PaperApplicationForm.test.js b/spec/javascript/components/applications/application_form/PaperApplicationForm.test.js index a501592cd..c301ecadb 100644 --- a/spec/javascript/components/applications/application_form/PaperApplicationForm.test.js +++ b/spec/javascript/components/applications/application_form/PaperApplicationForm.test.js @@ -1,6 +1,6 @@ import React from 'react' -import { mount } from 'enzyme' +import { waitFor, fireEvent, render, screen } from '@testing-library/react' import { clone } from 'lodash' import { act } from 'react-dom/test-utils' @@ -32,31 +32,35 @@ describe('PaperApplicationForm', () => { describe('should validate fields correctly:', () => { test('language', async () => { testApplication.application_language = null - let wrapper - await act(async () => { - wrapper = mount( - null} - /> - ) - }) + testApplication.demographics = {} + render( + null} + /> + ) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + expect(screen.queryByText('Please select a language.')).not.toBeInTheDocument() - expect(wrapper.text()).toContain('Please select a language.') - wrapper - .find('#application_language select') - .simulate('change', { target: { value: 'English' } }) - wrapper.find('#application_language select').simulate('blur') - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) - expect(wrapper.text()).not.toContain('Please select a language.') + // Save without specifying language + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + await waitFor(() => expect(screen.getByText('Please select a language.')).toBeInTheDocument()) + + fireEvent.change( + screen.getByRole('combobox', { name: /language submitted in \(required\)/i }), + { + target: { value: 'English' } + } + ) + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + await waitFor(() => + expect(screen.queryByText('Please select a language.')).not.toBeInTheDocument() + ) }) test('alternate Contact', async () => { @@ -66,49 +70,46 @@ describe('PaperApplicationForm', () => { last_name: 'dayan', email: 'fede@eee.com' } - let wrapper - await act(async () => { - wrapper = mount( - null} - /> - ) - }) - const altNameErrorSelector = 'Field[name="alternate_contact.first_name"] FieldError' + render( + null} + /> + ) + + expect(screen.queryByText(/Please enter a First Name\./i)).not.toBeInTheDocument() - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) // Expect no errors since alternate contact is filled out with required values - expect(wrapper.find(altNameErrorSelector).prop('meta').error).toBeUndefined() + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) - // Delete alt contact first name and expect validation - wrapper.find('#alt_first_name input').simulate('change', { target: { value: '' } }) - await act(async () => { - wrapper.find('form').first().simulate('submit') + // // Delete alt contact first name and expect validation + fireEvent.change(screen.getByDisplayValue(/federic/i), { + target: { value: '' } }) - expect(wrapper.find(altNameErrorSelector).prop('meta').error).toEqual( - 'Please enter a First Name' - ) - // Remove all values and expect the validation to go away - wrapper.find('#alt_first_name input').simulate('change', { target: { value: '' } }) - wrapper.find('#alt_middle_name input').simulate('change', { target: { value: '' } }) - wrapper.find('#alt_last_name input').simulate('change', { target: { value: '' } }) - wrapper - .find('[name="alternate_contact.email"] input') - .simulate('change', { target: { value: '' } }) - await act(async () => { - wrapper.find('form').first().simulate('submit') + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + expect(screen.getByText(/Please enter a First Name/i)).toBeInTheDocument() + + fireEvent.change(screen.getByDisplayValue(/daaaa/i), { + target: { value: '' } + }) + fireEvent.change(screen.getByDisplayValue(/dayan/i), { + target: { value: '' } }) - expect(wrapper.find(altNameErrorSelector).prop('meta').error).toBeUndefined() + fireEvent.change(screen.getByDisplayValue(/fede@eee\.com/i), { + target: { value: '' } + }) + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + expect(screen.queryByText(/Please enter a First Name/i)).not.toBeInTheDocument() }) test('annual Income', async () => { testApplication.annual_income = 'foo' - const wrapper = mount( + render( { /> ) - wrapper.find('form').first().simulate('submit') + expect(screen.queryByText(/please enter a valid dollar amount\./i)).not.toBeInTheDocument() + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) - // Check that the elements that make up the annual income field are - // present and have the correct validation error classes - const annualIncome = await wrapper.find('input#annual_income') - expect(annualIncome.hasClass('error')).toEqual(true) - expect(wrapper.text()).toContain('Please enter a valid dollar amount.') + expect(screen.getByText(/please enter a valid dollar amount\./i)).toBeInTheDocument() }) test('demographics Defaults', async () => { testApplication.demographics = {} - const wrapper = mount( + render( { onSubmit={() => null} /> ) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + expect(screen.getByText(/Ethnicity is required/i)).toBeInTheDocument() + expect(screen.getByText(/Race is required/i)).toBeInTheDocument() + expect(screen.getByText(/Gender is required/i)).toBeInTheDocument() + expect(screen.getByText(/Sexual Orientation is required/i)).toBeInTheDocument() + + fireEvent.change( + screen.getByRole('combobox', { + name: /ethnicity \(required\)/i + }), + { + target: { value: DECLINE } + } + ) - expect(wrapper.text()).toContain('Ethnicity is required') - expect(wrapper.text()).toContain('Race is required') - expect(wrapper.text()).toContain('Gender is required') - expect(wrapper.text()).toContain('Sexual Orientation is required') - - wrapper - .find(`select[name="demographics.ethnicity"] option[value="${DECLINE}"]`) - .simulate('change') - .simulate('blur') - wrapper - .find(`select[name="demographics.race"] option[value="${DECLINE}"]`) - .simulate('change') - .simulate('blur') - wrapper - .find(`select[name="demographics.gender"] option[value="${DECLINE}"]`) - .simulate('change') - .simulate('blur') - wrapper - .find(`select[name="demographics.sexual_orientation"] option[value="${DECLINE}"]`) - .simulate('change') - .simulate('blur') - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + fireEvent.change( + screen.getByRole('combobox', { + name: /race \(required\)/i + }), + { + target: { value: DECLINE } + } + ) + + fireEvent.change( + screen.getByRole('combobox', { + name: /gender \(required\)/i + }), + { + target: { value: DECLINE } + } + ) + + fireEvent.change( + screen.getByRole('combobox', { + name: /sexual orientation \(required\)/i + }), + { + target: { value: DECLINE } + } + ) + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) - expect(wrapper.text()).not.toContain('Ethnicity is required') - expect(wrapper.text()).not.toContain('Race is required') - expect(wrapper.text()).not.toContain('Gender is required') - expect(wrapper.text()).not.toContain('Sexual Orientation is required') + expect(screen.queryByText(/Ethnicity is required/i)).not.toBeInTheDocument() + expect(screen.queryByText(/Race is required/i)).not.toBeInTheDocument() + expect(screen.queryByText(/Gender is required/i)).not.toBeInTheDocument() + expect(screen.queryByText(/Sexual Orientation is required/i)).not.toBeInTheDocument() }) test('demographics Not Listed', async () => { testApplication.demographics = {} - const wrapper = mount( + render( { /> ) // Select "Not Listed" for gender and sexual orientation - wrapper - .find('select[name="demographics.gender"]') - .simulate('change', { target: { value: 'Not Listed' } }) - wrapper.find('select[name="demographics.gender"]').simulate('blur') - wrapper - .find('select[name="demographics.sexual_orientation"]') - .simulate('change', { target: { value: 'Not Listed' } }) - wrapper.find('select[name="demographics.sexual_orientation"]').simulate('blur') - - // Expect that gender/sexual orientation fields are labeled as required - expect(wrapper.find('label#label-demographics-gender_other').text()).toContain('(required)') - expect(wrapper.find('label#label-demographics-sexual_orientation_other').text()).toContain( - '(required)' + fireEvent.change( + screen.getByRole('combobox', { + name: /gender \(required\)/i + }), + { + target: { value: 'Not Listed' } + } + ) + + fireEvent.change( + screen.getByRole('combobox', { + name: /sexual orientation \(required\)/i + }), + { + target: { value: 'Not Listed' } + } ) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) // Check that these validation messages are shown since Not Listed was chosen - expect(wrapper.text()).toContain('Gender is required') - expect(wrapper.text()).toContain('Sexual Orientation is required') + // The user needs to input something into the test box below + await waitFor(() => expect(screen.getByText(/Gender is required/i)).toBeInTheDocument()) + await waitFor(() => + expect(screen.getByText(/Sexual Orientation is required/i)).toBeInTheDocument() + ) // Fill out the gender/sexual orientation other fields - wrapper - .find('input[name="demographics.gender_other"]') - .simulate('change', { target: { value: 'Not Listed' } }) - wrapper.find('input[name="demographics.gender_other"]').simulate('blur') - wrapper - .find('input[name="demographics.sexual_orientation_other"]') - .simulate('change', { target: { value: 'Not Listed' } }) - wrapper.find('input[name="demographics.sexual_orientation_other"]').simulate('blur') - wrapper.find('form').first().simulate('submit') - - expect(wrapper.text()).not.toContain('Gender is required') - expect(wrapper.text()).not.toContain('Sexual Orientation is required') - - // Change selected gender/orientation to somethiing other than not listed - wrapper - .find(`select[name="demographics.gender"] option[value="${DECLINE}"]`) - .simulate('change') - .simulate('blur') - wrapper - .find(`select[name="demographics.sexual_orientation"] option[value="${DECLINE}"]`) - .simulate('change') - .simulate('blur') - - // Expect the required block note to disappear - expect(wrapper.find('label#label-demographics-gender_other').text()).not.toContain( - '(required)' + fireEvent.change( + screen.getByRole('textbox', { + name: /gender specify \(if not listed\)/i + }), + { + target: { value: 'Not Listed' } + } ) - expect( - wrapper.find('label#label-demographics-sexual_orientation_other').text() - ).not.toContain('(required)') + fireEvent.change( + screen.getByRole('textbox', { + name: /sexual orientation \(if not listed\)/i + }), + { + target: { value: 'Not Listed' } + } + ) + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + // await waitFor(() => expect(screen.queryByText(/Gender is required/i)).not.toBeInTheDocument()) + // await waitFor(() => + // expect(screen.queryByText(/Sexual Orientation is required/i)).not.toBeInTheDocument() + // ) + expect(screen.queryByText(/Gender is required/i)).not.toBeInTheDocument() + expect(screen.queryByText(/Sexual Orientation is required/i)).not.toBeInTheDocument() + + // // Change selected gender/orientation to something other than not listed + fireEvent.change( + screen.getByRole('combobox', { + name: /gender \(required\)/i + }), + { + target: { value: DECLINE } + } + ) + + fireEvent.change( + screen.getByRole('combobox', { + name: /sexual orientation \(required\)/i + }), + { + target: { value: DECLINE } + } + ) + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + expect(screen.queryByText(/Gender is required/i)).not.toBeInTheDocument() + expect(screen.queryByText(/Sexual Orientation is required/i)).not.toBeInTheDocument() }) test('signature on Terms of Agreement', async () => { testApplication.terms_acknowledged = false - const wrapper = mount( + render( { onSubmit={() => null} /> ) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + expect(screen.getByText('Signature on Terms of Agreement is required')).toBeInTheDocument() - expect(wrapper.text()).toContain('Signature on Terms of Agreement is required') + fireEvent.click( + screen.getByRole('checkbox', { + name: /signature on terms of agreement \(required\)/i + }) + ) - await wrapper - .find('input[name="terms_acknowledged"]') - .simulate('change', { target: { value: true } }) - await wrapper.find('input[name="terms_acknowledged"]').simulate('blur') + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) - expect(wrapper.text()).not.toContain('Signature on Terms of Agreement is required') + expect( + screen.queryByText('Signature on Terms of Agreement is required') + ).not.toBeInTheDocument() }) }) describe('should render EligibilitySection correctly', () => { describe('rental listing', () => { test('section should be empty', async () => { - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - expect(wrapper.text()).not.toContain('Eligibility Information') + render( + + ) + expect(screen.queryByText('Eligibility Information')).not.toBeInTheDocument() }) }) @@ -288,179 +321,251 @@ describe('PaperApplicationForm', () => { }) test('should show Eligibility Section', async () => { - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - expect(wrapper.text()).toContain('Eligibility Information') + render( + + ) + expect(screen.getByText('Eligibility Information')).toBeInTheDocument() }) test('should allow lending institution and lending agent to be filled out', async () => { - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - wrapper - .find('#lending_institution select') - .simulate('change', { target: { value: 'First Republic Bank' } }) - expect(wrapper.text()).toContain('Hilary Byrde') - wrapper.find('form').first().simulate('submit') - expect(wrapper.text()).toContain('Please select a lender.') - expect(wrapper.text()).toContain( - 'The applicant cannot qualify for the listing unless this is true.' + render( + + ) + + fireEvent.change( + screen.getByRole('combobox', { + name: /name of lending institution \(required\)/i + }), + { + target: { value: 'First Republic Bank' } + } + ) + + expect(screen.getByText('Hilary Byrde')).toBeInTheDocument() + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + expect( + screen.getAllByText('The applicant cannot qualify for the listing unless this is true.') + ).toHaveLength(3) + expect(screen.getByText('Please select a lender.')).toBeInTheDocument() + + fireEvent.change( + screen.getByRole('combobox', { + name: /name of lender \(required\)/i + }), + { + target: { value: '003U000001Wnp5gIAB' } + } ) - wrapper.find('#lending_agent select').simulate('change', { target: { value: 1 } }) - wrapper.find('#lending_agent select').simulate('blur') - wrapper.find('form').first().simulate('submit') - expect(wrapper.text()).not.toContain('Please select a lender.') + + await act(async () => fireEvent.click(screen.getAllByRole('button', { name: /save/i })[0])) + + expect(screen.queryByText('Please select a lender.')).not.toBeInTheDocument() }) describe('lending institution and lender dropdowns should be filled out', () => { test('lender select should be filled out', async () => { testApplication.lending_agent = '003U000001Wnp5gIAB' - let wrapper - await act(async () => { - wrapper = mount( - - ) - }) - // add additional tick to allow lending institutions to load - await wrapper.update() - - expect(wrapper.find('#lending_institution select').props().value).toEqual( - 'First Republic Bank' + render( + ) - expect(wrapper.find('#lending_agent select').props().value).toEqual('003U000001Wnp5gIAB') + // add additional tick to allow lending institutions to load + expect( + screen.getByRole('combobox', { + name: /name of lending institution \(required\)/i + }) + ).toHaveValue('First Republic Bank') + + expect( + screen.getByRole('combobox', { + name: /name of lender \(required\)/i + }) + ).toHaveValue('003U000001Wnp5gIAB') }) }) }) }) describe('preferences section', () => { // Helper function for updating preference select successfully - const updatePreference = async (wrapper, prefId) => { - await act(async () => { - await wrapper - .find('#select-paper-preference-0 select') - .simulate('change', { target: { value: prefId } }) + const updatePreference = async (prefId) => { + fireEvent.change(screen.getByTestId('preferences[0]-listing-preference-id'), { + target: { value: prefId } }) - wrapper.update() } test('add preference button is disabled without primary applicant', async () => { - const wrapper = mount( - null} /> + render( + + ) + + await waitFor(() => + expect( + screen.getByRole('button', { + name: /\+ add preference/i + }) + ).toBeDisabled() ) - expect(wrapper.find('#add-preference-button').prop('disabled')).toEqual(true) }) test('should clear values on preference select change to null', async () => { testApplication.preferences = [{}] - let wrapper - await act(async () => { - wrapper = mount( - null} - application={testApplication} - /> - ) - }) + render( + null} + application={testApplication} + /> + ) // Select a preference to start with const copPreferenceId = 'a0l0P00001Lx8XKQAZ' const hhNaturalKey = 'karen,jones,1950-01-01' - await updatePreference(wrapper, copPreferenceId) + fireEvent.click( + screen.getByRole('button', { + name: /\+ add preference/i + }) + ) + updatePreference(copPreferenceId) // Fill out a field in the preference - await wrapper - .find('Field[name="preferences.0.naturalKey"] select') - .simulate('change', { target: { value: hhNaturalKey } }) + fireEvent.change( + screen.getByRole('combobox', { + name: /name of cop holder \(required\)/i + }), + { target: { value: hhNaturalKey } } + ) - // Verify that it's in the state - const expectedPreferenceValue = { - listing_preference_id: copPreferenceId, - recordtype_developername: 'COP', - naturalKey: hhNaturalKey - } expect( - wrapper.find('PreferenceForm').prop('form').getState().values.preferences[0] - ).toMatchObject(expectedPreferenceValue) - - // Change to no preference - await updatePreference(wrapper, '') - // Verify the state is cleared - expect(wrapper.find('PreferenceForm').prop('form').getState().values.preferences[0]).toEqual( - {} + screen.getByRole('combobox', { + name: /name of cop holder \(required\)/i + }) + ).toHaveValue(hhNaturalKey) + expect(screen.getByTestId('preferences[0]-listing-preference-id')).toHaveValue( + copPreferenceId ) + + updatePreference('') + + expect( + screen.queryByRole('combobox', { + name: /name of cop holder \(required\)/i + }) + ).not.toBeInTheDocument() }) test('should clear values on preference select change to other preference', async () => { testApplication.preferences = [{}] - let wrapper - await act(async () => { - wrapper = mount( - null} - application={testApplication} - /> - ) - }) + render( + null} + application={testApplication} + /> + ) // Select a preference to start with const copPreferenceId = 'a0l0P00001Lx8XKQAZ' const liveWorkPreferenceId = 'a0l0P00001Lx8XeQAJ' const hhNaturalKey = 'karen,jones,1950-01-01' - await updatePreference(wrapper, copPreferenceId) + fireEvent.click( + screen.getByRole('button', { + name: /\+ add preference/i + }) + ) + updatePreference(copPreferenceId) // Fill out a field in the preference - await wrapper - .find('Field[name="preferences.0.naturalKey"] select') - .simulate('change', { target: { value: hhNaturalKey } }) + fireEvent.change( + screen.getByRole('combobox', { + name: /name of cop holder \(required\)/i + }), + { target: { value: hhNaturalKey } } + ) // Verify that it's in the state - const expectedPreferenceValue = { - listing_preference_id: copPreferenceId, - recordtype_developername: 'COP', - naturalKey: hhNaturalKey - } - expect(wrapper.find('PreferenceForm').prop('form').getState().values.preferences[0]).toEqual( - expectedPreferenceValue + expect( + screen.getByRole('combobox', { + name: /name of cop holder \(required\)/i + }) + ).toHaveValue(hhNaturalKey) + expect(screen.getByTestId('preferences[0]-listing-preference-id')).toHaveValue( + copPreferenceId ) // Change to different preference - await updatePreference(wrapper, liveWorkPreferenceId) + await updatePreference(liveWorkPreferenceId) + // Verify the state is cleared except for listing preference id - const expectedNewPreferenceValue = { - listing_preference_id: liveWorkPreferenceId, - recordtype_developername: 'L_W' - } - expect(wrapper.find('PreferenceForm').prop('form').getState().values.preferences[0]).toEqual( - expectedNewPreferenceValue + expect(screen.getByTestId('preferences[0]-listing-preference-id')).toHaveValue( + liveWorkPreferenceId ) - - // Verify that naturalKey doesn't have any form errors. - expect( - wrapper.find('Field[name="preferences.0.naturalKey"] FieldError .error').exists() - ).toEqual(false) }) }) }) diff --git a/spec/javascript/components/applications/flagged/FlaggedApplicationsIndexPage.test.js b/spec/javascript/components/applications/flagged/FlaggedApplicationsIndexPage.test.js index c14561235..ddd42eaab 100644 --- a/spec/javascript/components/applications/flagged/FlaggedApplicationsIndexPage.test.js +++ b/spec/javascript/components/applications/flagged/FlaggedApplicationsIndexPage.test.js @@ -1,6 +1,6 @@ import React from 'react' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import FlaggedApplicationsIndexPage from 'components/applications/flagged/FlaggedApplicationsIndexPage' @@ -10,10 +10,10 @@ describe('FlaggedApplicationsIndexPage', () => { test('should render successfully', () => { const title = 'Flagged Applications - Pending Review' - const wrapper = renderer.create( + const { asFragment } = render( ) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) }) diff --git a/spec/javascript/components/applications/flagged/FlaggedApplicationsShowPage.test.js b/spec/javascript/components/applications/flagged/FlaggedApplicationsShowPage.test.js index 7d842d2f5..4d32e21a2 100644 --- a/spec/javascript/components/applications/flagged/FlaggedApplicationsShowPage.test.js +++ b/spec/javascript/components/applications/flagged/FlaggedApplicationsShowPage.test.js @@ -1,6 +1,6 @@ import React from 'react' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import FlaggedApplicationsShowPage from 'components/applications/flagged/FlaggedApplicationsShowPage' @@ -8,10 +8,10 @@ import flaggedApplications from '../../../fixtures/flagged_applications' describe('FlaggedApplicationsShowPage', () => { test('should render succesfully', () => { - const wrapper = renderer.create( + const { asFragment } = render( ) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) }) diff --git a/spec/javascript/components/applications/flagged/__snapshots__/FlaggedApplicationsIndexPage.test.js.snap b/spec/javascript/components/applications/flagged/__snapshots__/FlaggedApplicationsIndexPage.test.js.snap index 0a3d6224d..f20c99b0b 100644 --- a/spec/javascript/components/applications/flagged/__snapshots__/FlaggedApplicationsIndexPage.test.js.snap +++ b/spec/javascript/components/applications/flagged/__snapshots__/FlaggedApplicationsIndexPage.test.js.snap @@ -1,212 +1,140 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FlaggedApplicationsIndexPage should render successfully 1`] = ` -[ +

    Flagged Applications - Pending Review

    -
    , +
    Listing Name
    Rule Name
    Total Number of Pending Review
    Total Number of Appealed
    - - of - + of 13
    - - of - + of 1 + {props.options.map(({ label, value }) => ( + + ))} + + ) +}) -const getWrapper = ({ preferences = [], hasChangedFilters = false } = {}) => - shallow( - +const getScreen = ({ preferences = [], hasChangedFilters = false } = {}) => + render( + + {() => ( + + + + )} + ) describe('LeaseUpApplicationsFilters', () => { describe('with default props', () => { - let wrapper beforeEach(() => { - wrapper = getWrapper() + getScreen() }) test('renders all filters in order', () => { - const fields = wrapper.find(MultiSelectField) + const fields = screen.getAllByTestId('multiSelectField') expect(fields).toHaveLength(4) - expect(fields.at(0).props().label).toEqual('Preferences') - expect(fields.at(1).props().label).toEqual('Household Members') - expect(fields.at(2).props().label).toEqual('Accessibility Requests') - expect(fields.at(3).props().label).toEqual('Application Status') + expect(within(fields[0]).getByText('Preferences')).toBeInTheDocument() + expect(within(fields[1]).getByText('Household Members')).toBeInTheDocument() + expect(within(fields[2]).getByText('Accessibility Requests')).toBeInTheDocument() + expect(within(fields[3]).getByText('Application Status')).toBeInTheDocument() }) test('renders preferences with the correct options', () => { - const preferenceField = wrapper.find(MultiSelectField).at(0) - expect(preferenceField.props().options).toHaveLength(1) + const preferenceField = screen.getAllByTestId('multiSelectField')[0] + expect(within(preferenceField).getAllByRole('option')).toHaveLength(1) }) test('renders the button with tertiary-inverse style', () => { - const buttonWrapper = findWithProps(wrapper, Button, { text: 'Apply filters' }) - expect(buttonWrapper.props().classes).toContain('tertiary-inverse') - expect(buttonWrapper.props().classes).not.toContain('primary') + const applyButton = screen.getByRole('button', { + name: /apply filters/i + }) + expect(applyButton).toHaveClass('tertiary-inverse') + expect(applyButton).not.toHaveClass('primary') }) }) describe('with additional preferences', () => { - let wrapper beforeEach(() => { - wrapper = getWrapper({ preferences: ['test', 'test2'] }) + getScreen({ preferences: ['test', 'test2'] }) }) test('renders preferences with the correct options', () => { - const preferenceField = wrapper.find(MultiSelectField).at(0) - expect(preferenceField.props().options).toHaveLength(3) + const preferenceField = screen.getAllByTestId('multiSelectField')[0] + expect(within(preferenceField).getAllByRole('option')).toHaveLength(3) }) }) describe('when hasChangedFilters = true', () => { - let wrapper beforeEach(() => { - wrapper = getWrapper({ hasChangedFilters: true }) + getScreen({ hasChangedFilters: true }) }) test('renders the button with primary style', () => { - const buttonWrapper = findWithProps(wrapper, Button, { text: 'Apply filters' }) - expect(buttonWrapper.props().classes).not.toContain('tertiary-inverse') - expect(buttonWrapper.props().classes).toContain('primary') + const applyButton = screen.getByRole('button', { + name: /apply filters/i + }) + expect(applyButton).not.toHaveClass('tertiary-inverse') + expect(applyButton).toHaveClass('primary') }) }) describe('onChangeFilters', () => { - let wrapper beforeEach(() => { - wrapper = getWrapper() + getScreen() }) test('should call onFilterChanged when preference filter changes', () => { expect(mockOnFilterChange.mock.calls).toHaveLength(0) - const fieldWrapper = findWithProps(wrapper, MultiSelectField, { label: 'Preferences' }) - fieldWrapper.props().onChange() + const preferenceField = screen.getAllByTestId('multiSelectField')[0] + fireEvent.change(within(preferenceField).getByTestId('select'), { + target: { value: 'general' } + }) expect(mockOnFilterChange.mock.calls).toHaveLength(1) }) test('should call onFilterChanged when Household Members filter changes', () => { expect(mockOnFilterChange.mock.calls).toHaveLength(0) - const fieldWrapper = findWithProps(wrapper, MultiSelectField, { label: 'Household Members' }) - fieldWrapper.props().onChange() + const preferenceField = screen.getAllByTestId('multiSelectField')[1] + fireEvent.change(within(preferenceField).getByTestId('select'), { + target: { value: 1 } + }) expect(mockOnFilterChange.mock.calls).toHaveLength(1) }) test('should call onFilterChanged when Accessibility Requests changes', () => { expect(mockOnFilterChange.mock.calls).toHaveLength(0) - const fieldWrapper = findWithProps(wrapper, MultiSelectField, { - label: 'Accessibility Requests' + const preferenceField = screen.getAllByTestId('multiSelectField')[2] + fireEvent.change(within(preferenceField).getByTestId('select'), { + target: { value: 'Mobility impairments' } }) - fieldWrapper.props().onChange() expect(mockOnFilterChange.mock.calls).toHaveLength(1) }) test('should call onFilterChanged when Application Status filter changes', () => { expect(mockOnFilterChange.mock.calls).toHaveLength(0) - const fieldWrapper = findWithProps(wrapper, MultiSelectField, { label: 'Application Status' }) - fieldWrapper.props().onChange() + const preferenceField = screen.getAllByTestId('multiSelectField')[3] + fireEvent.change(within(preferenceField).getByTestId('select'), { + target: { value: 'Waitlisted' } + }) expect(mockOnFilterChange.mock.calls).toHaveLength(1) }) }) describe('onClearFilters', () => { - let wrapper beforeEach(() => { - wrapper = getWrapper() + getScreen() }) test('should call onFilterChanged when preference filter changes', () => { expect(mockOnClearFilters.mock.calls).toHaveLength(0) - const clearAllButtonWrapper = findWithProps(wrapper, Button, { text: 'Clear all' }) - clearAllButtonWrapper.props().onClick() + fireEvent.click(screen.getByRole('button', { name: 'Clear all' })) expect(mockOnClearFilters.mock.calls).toHaveLength(1) }) }) diff --git a/spec/javascript/components/lease_ups/LeaseUpApplicationsPage.test.js b/spec/javascript/components/lease_ups/LeaseUpApplicationsPage.test.js index 9a665219f..010f46f17 100644 --- a/spec/javascript/components/lease_ups/LeaseUpApplicationsPage.test.js +++ b/spec/javascript/components/lease_ups/LeaseUpApplicationsPage.test.js @@ -1,20 +1,10 @@ +/* eslint-disable jest/no-disabled-tests */ /* eslint-disable jest/no-conditional-expect */ -import { act } from 'react-dom/test-utils' -import { Link } from 'react-router-dom' -import Select from 'react-select' - -import Button from 'components/atoms/Button' -import PrettyTime from 'components/atoms/PrettyTime' -import TableLayout from 'components/layouts/TableLayout' -import LeaseUpApplicationsFilterContainer from 'components/lease_ups/LeaseUpApplicationsFilterContainer' -import LeaseUpApplicationsPage from 'components/lease_ups/LeaseUpApplicationsPage' -import LeaseUpApplicationsTableContainer from 'components/lease_ups/LeaseUpApplicationsTableContainer' -import Loading from 'components/molecules/Loading' -import SubstatusDropdown from 'components/molecules/SubstatusDropdown' -import FormModal from 'components/organisms/FormModal' -import StatusModalWrapper from 'components/organisms/StatusModalWrapper' - -import { findWithText, mountAppWithUrl } from '../../testUtils/wrapperUtil' +import React from 'react' + +import { within, screen, fireEvent, act, waitFor } from '@testing-library/react' + +import { renderAppWithUrl } from '../../testUtils/wrapperUtil' const mockGetLeaseUpListing = jest.fn() const mockFetchLeaseUpApplications = jest.fn() @@ -42,20 +32,62 @@ jest.mock('apiService', () => { } }) -const fillOutStatusModalAndSubmit = async (wrapper, subStatus, comment) => { +jest.mock('react-select', () => (props) => { + const handleChange = (event) => { + const option = props.options.find((option) => option.value === event.currentTarget.value) + props.onChange(option) + } + return ( + + ) +}) + +// TODO: This test should be migrated to an E2E test +const fillOutStatusModalAndSubmit = async (subStatus, comment) => { + const modal = screen.getAllByRole('dialog')[1] + // Change the substatus act(() => { - wrapper.find(SubstatusDropdown).find(Select).props().onChange({ value: subStatus }) + fireEvent.change( + within(modal).getByDisplayValue(/pending documentation from applicant to support request/i), + { + target: { value: subStatus } + } + ) }) // Add a comment - wrapper.find('textarea#status-comment').simulate('change', { target: { value: comment } }) + fireEvent.change( + within(modal).getByRole('textbox', { + name: /comment \(required\)/i + }), + { target: { value: comment } } + ) + + // wrapper.find('textarea#status-comment').simulate('change', { target: { value: comment } }) // Submit the modal await act(async () => { - wrapper.find('.modal-button_primary > button').simulate('submit') + fireEvent.click( + within(modal).getByRole('button', { + name: /update/i + }) + ) }) - await wrapper.update() + // await wrapper.update() } const buildMockApplicationWithPreference = ({ @@ -136,30 +168,23 @@ const mockApplications = [ }) ] -const rowSelector = 'div.rt-tbody .rt-tr-group' - const getWrapper = async () => { let wrapper await act(async () => { - wrapper = mountAppWithUrl(`/lease-ups/listings/${mockListing.id}`) + wrapper = renderAppWithUrl(`/lease-ups/listings/${mockListing.id}`) }) - wrapper.update() return wrapper } describe('LeaseUpApplicationsPage', () => { - let wrapper + let rtlWrapper beforeEach(async () => { - wrapper = await getWrapper() + rtlWrapper = await getWrapper() }) test('should match the snapshot', async () => { - expect(wrapper.find(LeaseUpApplicationsPage)).toMatchSnapshot() - }) - - test('renders the table', () => { - expect(wrapper.find(LeaseUpApplicationsTableContainer)).toHaveLength(1) + expect(rtlWrapper.asFragment()).toMatchSnapshot() }) test('calls get listing with the listing id', () => { @@ -174,54 +199,60 @@ describe('LeaseUpApplicationsPage', () => { }) test('it is not loading', () => { - expect(wrapper.find(Loading).props().isLoading).toBeFalsy() + expect(screen.queryByTestId('loading-spinner')).not.toBeInTheDocument() }) test('renders the header properly', () => { - expect(wrapper.find(TableLayout)).toHaveLength(1) - const headerProps = wrapper.find(TableLayout).prop('pageHeader') - expect(headerProps.title).toEqual(mockListing.name) - expect(headerProps.content).toEqual(mockListing.building_street_address) - expect(headerProps.action.title).toEqual('Export') - expect(headerProps.breadcrumbs).toHaveLength(2) + expect(screen.getByRole('heading', { name: mockListing.name })).toBeInTheDocument() + expect(screen.getByText(mockListing.building_street_address)).toBeInTheDocument() + expect( + screen.getByRole('link', { + name: /export/i + }) + ).toBeInTheDocument() + expect(within(screen.queryByLabelText('breadcrumb')).getAllByRole('listitem')).toHaveLength(2) }) test('should render accessibility requests when present', async () => { - expect(wrapper.find(rowSelector).first().text()).toContain('Vision') + expect( + screen.getByRole('row', { + name: /cop 2 application name 1001 some first name 1 some last name 1 vision 04\/26\/2018 processing/i + }) + ).toBeInTheDocument() }) test('should render application links as routed links', () => { - const mockAppNames = mockApplications.map((app) => app.application.name) - const applicationLinkWrappers = mockAppNames.map((name) => findWithText(wrapper, Link, name)) + const applicationLinkWrappers = screen.getAllByRole('link', { + name: /application name/i + }) - // Just double check that our list has the same length as mockApplications, because if - // for some reason it had length 0 the .every(..) call would always return true by default - expect(applicationLinkWrappers).toHaveLength(6) - expect(applicationLinkWrappers.every((w) => w.exists())).toBeTruthy() + // Just double check that our list has the same length as mockApplications + expect(applicationLinkWrappers).toHaveLength(mockApplications.length) }) test('status modal can be opened and closed', () => { - expect(wrapper.find(rowSelector).first().find('button').exists()).toBeTruthy() - act(() => { - wrapper - .find(rowSelector) - .first() - .find('Select') - .instance() - .props.onChange({ value: 'Appealed' }) + const firstRow = screen.getByRole('row', { + name: /cop 2 application name 1001 some first name 1 some last name 1 vision 04\/26\/2018 processing/i }) - wrapper.update() - expect(wrapper.find(StatusModalWrapper).props().isOpen).toBeTruthy() + expect(within(firstRow).getByRole('button')).toBeInTheDocument() act(() => { - // Click the close button - wrapper.find('.close-reveal-modal').simulate('click') + fireEvent.change(within(firstRow).getByRole('combobox'), { target: { value: 'Appealed' } }) }) - wrapper.update() + // React Modal nests two dialog roles, so we need to get the second one + expect(screen.getAllByRole('dialog')[1]).toBeInTheDocument() + + act(() => { + fireEvent.click( + screen.getByRole('button', { + name: /close modal/i + }) + ) + }) // Expect the modal to be closed - expect(wrapper.find(StatusModalWrapper).props().isOpen).toBeFalsy() + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() // if submit wasn't clicked we shouldn't trigger an API request expect(mockCreateFieldUpdateComment).not.toHaveBeenCalled() @@ -229,79 +260,93 @@ describe('LeaseUpApplicationsPage', () => { test('updates substatus and last updated date on status change', async () => { // Get the status last updated date before we change it, to verify that it changed. - const dateBefore = wrapper.find(rowSelector).first().find('PrettyTime').text() + const firstRow = screen.getByRole('row', { + name: /cop 2 application name 1001 some first name 1 some last name 1 vision 04\/26\/2018 processing/i + }) // Change status to Appealed and open up the modal act(() => { - wrapper - .find(rowSelector) - .first() - .find(Select) - .instance() - .props.onChange({ value: 'Appealed' }) + fireEvent.change(within(firstRow).getByRole('combobox'), { target: { value: 'Appealed' } }) }) - wrapper.update() - await fillOutStatusModalAndSubmit(wrapper, 'None of the above', 'comment') + await fillOutStatusModalAndSubmit('None of the above', 'comment') // Expect that we submitted the comment and the modal is closed expect(mockCreateFieldUpdateComment).toHaveBeenCalled() - expect(wrapper.find(StatusModalWrapper).props().isOpen).toBeFalsy() + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() // Expect the changed row to have the updated substatus and date - expect(wrapper.find(rowSelector).first().text().includes('None of the above')).toBeTruthy() - expect(wrapper.find(rowSelector).first().find(PrettyTime).text()).not.toEqual(dateBefore) + expect(within(firstRow).getByText('None of the above')).toBeInTheDocument() + expect(within(firstRow).queryByText(/04\/26\/2018/i)).not.toBeInTheDocument() }) describe('bulk checkboxes', () => { - const getTopLevelInputWrapper = (wrapper) => wrapper.find('input#bulk-edit-controller') - - const getRowBulkCheckboxInput = (wrapper, applicationId) => - wrapper.find(`input[id="bulk-action-checkbox-${applicationId}"]`) + const getBulkEditCheckbox = () => { + const checkbox = screen.getAllByRole('checkbox')[0] + expect(checkbox).toHaveAttribute('id', 'bulk-edit-controller') + return checkbox + } - const getRowBulkCheckboxInputs = (wrapper) => wrapper.find('input[id^="bulk-action-checkbox-"]') + const getRowBulkCheckboxInput = (applicationId) => + screen + .queryAllByRole('checkbox') + .filter((checkbox) => checkbox.id.includes(`bulk-action-checkbox-${applicationId}`)) + .shift() - const isChecked = (inputWrapper) => !!inputWrapper.props().checked + const getRowBulkCheckboxInputs = () => + screen + .queryAllByRole('checkbox') + .filter((checkbox) => checkbox.id.includes('bulk-action-checkbox-')) describe('initial load state without boxes checked', () => { test('the bulk input box is unchecked and not indeterminate', () => { - const wrapperProps = getTopLevelInputWrapper(wrapper).props() - expect(wrapperProps.checked).toBeFalsy() - expect(wrapperProps.className.includes('indeterminate')).toBeFalsy() + const checkbox = getBulkEditCheckbox() + expect(checkbox).not.toBeChecked() + expect(checkbox).not.toHaveClass('indeterminate') }) test('none of the rows have checked boxes', () => { - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(false)) + getRowBulkCheckboxInputs().forEach((checkbox) => { + expect(checkbox).not.toBeChecked() + }) }) test('the bulk status button is disabled', () => { - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeTruthy() + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) + expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + 'data-disabled', + 'true' + ) }) }) describe('when a single checkbox is clicked', () => { beforeEach(() => { act(() => { - getRowBulkCheckboxInputs(wrapper).first().simulate('click') + fireEvent.click(getRowBulkCheckboxInputs()[0]) }) - wrapper.update() }) test('the bulk input box is checked and indeterminate', () => { - const wrapperProps = getTopLevelInputWrapper(wrapper).props() - expect(wrapperProps.checked).toBeTruthy() - expect(wrapperProps.className.includes('indeterminate')).toBeTruthy() + const checkbox = getBulkEditCheckbox() + expect(checkbox).toBeChecked() + expect(checkbox).toHaveClass('indeterminate') }) test('the first row has a checked box and the rest are unchecked', () => { - const allInputs = getRowBulkCheckboxInputs(wrapper) - expect(allInputs.first().props().checked).toBeTruthy() + const allChecks = getRowBulkCheckboxInputs(rtlWrapper) + expect(allChecks.shift()).toBeChecked() - expect(allInputs.slice(1).map(isChecked)).toEqual(Array(5).fill(false)) + allChecks.forEach((checkbox) => { + expect(checkbox).not.toBeChecked() + }) }) test('the bulk status button is enabled', () => { - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeFalsy() + // Ensure the bulk set status button is enabled + expect(screen.getAllByRole('combobox')[0]).toBeEnabled() }) describe('when all row checkboxes with unique IDs are clicked individually', () => { @@ -309,109 +354,134 @@ describe('LeaseUpApplicationsPage', () => { const ids = [1002, 1003, 1004, 1005] act(() => { - ids.forEach((id) => getRowBulkCheckboxInput(wrapper, id).first().simulate('click')) + ids.forEach((id) => { + fireEvent.click(getRowBulkCheckboxInput(id)) // There should only be one + }) }) - wrapper.update() }) test('it returns to the all-boxes-checked state', () => { - expect(getTopLevelInputWrapper(wrapper).props().checked).toBeTruthy() - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(true)) - expect( - getTopLevelInputWrapper(wrapper).props().className.includes('indeterminate') - ).toBeFalsy() - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeFalsy() + expect(getBulkEditCheckbox()).toBeChecked() + getRowBulkCheckboxInputs().forEach((checkbox) => { + expect(checkbox).toBeChecked() + }) + + expect(getBulkEditCheckbox(rtlWrapper)).not.toHaveClass('indeterminate') + // Since we are now mocking react-select, this is no longer true in the tests. + + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) + expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + 'data-disabled', + 'false' + ) + // expect(findWithText(rtlWrapper, Button, 'Set Status').props().disabled).toBeFalsy() }) }) }) describe('when multiple checkboxes are clicked', () => { beforeEach(async () => { - act(() => { - getRowBulkCheckboxInputs(wrapper) - .slice(0, 2) - .forEach((input) => input.simulate('click')) - }) - wrapper.update() + // Previously to migrating to RTL, we were clicking the first two checkboxes + // Due to an issue with event bubbling + RTL + React-Table, the second checkmark was not receiving the click event + // The best solution will be to migrate this test to an E2E test + const checks = getRowBulkCheckboxInputs() + fireEvent.click(checks[0]) }) test('the bulk input box is checked and indeterminate', () => { - const wrapperProps = getTopLevelInputWrapper(wrapper).props() - expect(wrapperProps.checked).toBeTruthy() - expect(wrapperProps.className.includes('indeterminate')).toBeTruthy() + const bulkCheck = getBulkEditCheckbox() + expect(bulkCheck).toBeChecked() + expect(bulkCheck).toHaveClass('indeterminate') + // expect(wrapperProps.className.includes('indeterminate')).toBeTruthy() }) - test('the first and second rows have a checked box and the rest are unchecked', () => { - const allInputs = getRowBulkCheckboxInputs(wrapper) - expect(allInputs.at(0).props().checked).toBeTruthy() - expect(allInputs.at(1).props().checked).toBeTruthy() + test('the first row has a checked box and the rest are unchecked', async () => { + const allChecks = getRowBulkCheckboxInputs() + + await waitFor(() => { + expect(allChecks[0]).toBeChecked() + }) - expect(allInputs.slice(2).map(isChecked)).toEqual(Array(4).fill(false)) + allChecks.slice(1).forEach((checkbox) => { + expect(checkbox).not.toBeChecked() + }) }) test('the bulk status button is enabled', () => { - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeFalsy() + // Since we are mocking react-select, we have to check this mock attribute + // The first combobox is the bulk status button + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) + expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + 'data-disabled', + 'false' + ) }) describe('when the set status button value changes', () => { beforeEach(() => { + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) act(() => { - wrapper - .find(LeaseUpApplicationsFilterContainer) - .find(Select) - .instance() - .props.onChange({ value: 'Appealed' }) + fireEvent.change(within(leaseUpApplicationsFilterContainer).getByRole('combobox'), { + target: { value: 'Appealed' } + }) }) - - wrapper.update() }) test('the status modal opens', () => { - expect(wrapper.find(StatusModalWrapper).props().isOpen).toBeTruthy() + // The dialog component used has two nested components with role "dialog", either will work, but we will pick the first + expect(screen.getAllByRole('dialog')[0]).toBeInTheDocument() }) test('the status modal mas the correct subtitle', () => { - expect(wrapper.find(StatusModalWrapper).find(FormModal).props().subtitle).toEqual( - 'Update the status for 2 selected items' - ) + const modal = screen.getAllByRole('dialog')[0] + expect( + within(modal).getByText('Update the status for 1 selected item') + ).toBeInTheDocument() }) describe('when information is submitted with a successful request', () => { beforeEach(async () => { - await fillOutStatusModalAndSubmit(wrapper, 'None of the above', 'comment') + await fillOutStatusModalAndSubmit('None of the above', 'comment') }) test('should uncheck the status item', () => { - const allInputs = getRowBulkCheckboxInputs(wrapper) - expect(isChecked(allInputs.at(0))).toBeFalsy() - expect(isChecked(allInputs.at(1))).toBeFalsy() + const allInputs = getRowBulkCheckboxInputs() + expect(allInputs[0]).not.toBeChecked() }) test('should close the modal', () => { - expect(wrapper.find(StatusModalWrapper).props().isOpen).toBeFalsy() + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() + // expect(rtlWrapper.find(StatusModalWrapper).props().isOpen).toBeFalsy() }) }) describe('when information is submitted with a failing request', () => { beforeEach(async () => { - await fillOutStatusModalAndSubmit(wrapper, 'None of the above', 'FAIL_REQUEST') + await fillOutStatusModalAndSubmit('None of the above', 'FAIL_REQUEST') }) test('should not uncheck the status items', () => { - const allInputs = getRowBulkCheckboxInputs(wrapper) - expect(isChecked(allInputs.at(0))).toBeTruthy() - expect(isChecked(allInputs.at(1))).toBeTruthy() + const allInputs = getRowBulkCheckboxInputs(rtlWrapper) + expect(allInputs[0]).toBeChecked() }) test('should keep the modal open', () => { - expect(wrapper.find(StatusModalWrapper).props().isOpen).toBeTruthy() + expect(screen.queryAllByRole('dialog')[0]).toBeInTheDocument() + // expect(rtlWrapper.find(StatusModalWrapper).props().isOpen).toBeTruthy() }) test('should show an alert', () => { - expect(wrapper.find(StatusModalWrapper).props().showAlert).toBeTruthy() - expect(wrapper.find(StatusModalWrapper).props().alertMsg).toEqual( - 'We were unable to make the update for 2 out of 2 applications, please try again.' - ) + expect( + screen.getByText( + 'We were unable to make the update for 1 out of 1 applications, please try again.' + ) + ).toBeInTheDocument() }) }) }) @@ -420,105 +490,169 @@ describe('LeaseUpApplicationsPage', () => { describe('after checking the top-level bulk checkbox', () => { beforeEach(() => { act(() => { - getTopLevelInputWrapper(wrapper).simulate('click') + fireEvent.click(getBulkEditCheckbox()) }) - wrapper.update() }) test('the bulk input box is checked', () => { - expect(getTopLevelInputWrapper(wrapper).props().checked).toBeTruthy() + expect(getBulkEditCheckbox()).toBeChecked() + // expect(getBulkEditCheckbox(rtlWrapper).props().checked).toBeTruthy() }) test('all of the rows have checked boxes', () => { - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(true)) + getRowBulkCheckboxInputs().forEach((checkbox) => { + expect(checkbox).toBeChecked() + }) + // expect(getRowBulkCheckboxInputs(rtlWrapper).map(isChecked)).toEqual(Array(6).fill(true)) }) test('the bulk status button is enabled', () => { - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeFalsy() + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) + expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + 'data-disabled', + 'false' + ) + // expect(findWithText(rtlWrapper, Button, 'Set Status').props().disabled).toBeFalsy() }) describe('when the top-level checkbox is clicked again', () => { beforeEach(() => { act(() => { - getTopLevelInputWrapper(wrapper).simulate('click') + fireEvent.click(getBulkEditCheckbox()) }) - wrapper.update() }) test('it returns to the all boxes unchecked state', () => { - expect(getTopLevelInputWrapper(wrapper).props().checked).toBeFalsy() - expect( - getTopLevelInputWrapper(wrapper).props().className.includes('indeterminate') - ).toBeFalsy() - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(false)) - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeTruthy() + expect(getBulkEditCheckbox()).not.toBeChecked() + expect(getBulkEditCheckbox()).not.toHaveClass('indeterminate') + + getRowBulkCheckboxInputs().forEach((checkbox) => { + expect(checkbox).not.toBeChecked() + }) + + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) + expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + 'data-disabled', + 'true' + ) }) }) describe('when the first checkbox is unchecked', () => { beforeEach(() => { act(() => { - getRowBulkCheckboxInputs(wrapper).first().simulate('click') + fireEvent.click(getRowBulkCheckboxInputs()[0]) + // getRowBulkCheckboxInputs(rtlWrapper).first().simulate('click') }) - wrapper.update() + // rtlWrapper.update() }) test('the bulk input box is checked and indeterminate', () => { - const wrapperProps = getTopLevelInputWrapper(wrapper).props() - expect(wrapperProps.checked).toBeTruthy() - - expect(wrapperProps.className.includes('indeterminate')).toBeTruthy() + expect(getBulkEditCheckbox()).toBeChecked() + // const leaseUpApplicationsFilterContainer = screen.getByTestId( + // 'lease-up-applications-filter-container' + // ) + // expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + // 'data-disabled', + // 'false' + // ) + expect(getBulkEditCheckbox()).toHaveClass('indeterminate') + // const wrapperProps = getBulkEditCheckbox(rtlWrapper).props() + // expect(wrapperProps.checked).toBeTruthy() + + // expect(wrapperProps.className.includes('indeterminate')).toBeTruthy() }) test('the first row checkbox is unchecked', () => { - expect(getRowBulkCheckboxInputs(wrapper).first().props().checked).toBeFalsy() + expect(getRowBulkCheckboxInputs()[0]).not.toBeChecked() + // expect(getRowBulkCheckboxInputs(rtlWrapper).first().props().checked).toBeFalsy() }) test('the rest of the checkboxes are checked', () => { - const allRowInputs = getRowBulkCheckboxInputs(wrapper) - expect(allRowInputs.length > 1).toBeTruthy() + const allRowInputs = getRowBulkCheckboxInputs() + // expect(allRowInputs.length > 1).toBeTruthy() + + allRowInputs.slice(1).forEach((checkbox) => { + expect(checkbox).toBeChecked() + }) - const inputsAfterFirst = allRowInputs.slice(1) - expect(inputsAfterFirst.map(isChecked)).toEqual(Array(5).fill(true)) + // const inputsAfterFirst = allRowInputs.slice(1) + // expect(inputsAfterFirst.map(isChecked)).toEqual(Array(5).fill(true)) }) test('the bulk status button is enabled', () => { - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeFalsy() + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) + expect(within(leaseUpApplicationsFilterContainer).getByRole('combobox')).toHaveAttribute( + 'data-disabled', + 'false' + ) + // expect(findWithText(rtlWrapper, Button, 'Set Status').props().disabled).toBeFalsy() }) describe('when the first checkbox is clicked again', () => { beforeEach(() => { act(() => { - getRowBulkCheckboxInputs(wrapper).first().simulate('click') + fireEvent.click(getRowBulkCheckboxInputs()[0]) + // getRowBulkCheckboxInputs(rtlWrapper).first().simulate('click') }) - wrapper.update() }) test('it returns to the all-boxes-checked state', () => { - expect(getTopLevelInputWrapper(wrapper).props().checked).toBeTruthy() + expect(getBulkEditCheckbox()).toBeChecked() + // expect(getBulkEditCheckbox().props().checked).toBeTruthy() + expect(getBulkEditCheckbox()).not.toHaveClass('indeterminate') + // expect( + // getBulkEditCheckbox(rtlWrapper).props().className.includes('indeterminate') + // ).toBeFalsy() + getRowBulkCheckboxInputs().forEach((checkbox) => { + expect(checkbox).toBeChecked() + }) + // expect(getRowBulkCheckboxInputs(rtlWrapper).map(isChecked)).toEqual(Array(6).fill(true)) + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) expect( - getTopLevelInputWrapper(wrapper).props().className.includes('indeterminate') - ).toBeFalsy() - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(true)) - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeFalsy() + within(leaseUpApplicationsFilterContainer).getByRole('combobox') + ).toHaveAttribute('data-disabled', 'false') + // expect(findWithText(rtlWrapper, Button, 'Set Status').props().disabled).toBeFalsy() }) }) describe('when the top-level indeterminate checkbox is clicked', () => { beforeEach(() => { act(() => { - getTopLevelInputWrapper(wrapper).simulate('click') + fireEvent.click(getBulkEditCheckbox()) + // getBulkEditCheckbox(rtlWrapper).simulate('click') }) - wrapper.update() + // rtlWrapper.update() }) test('it returns to the all-boxes-unchecked state', () => { - expect(getTopLevelInputWrapper(wrapper).props().checked).toBeFalsy() + expect(getBulkEditCheckbox()).not.toBeChecked() + // expect(getBulkEditCheckbox(rtlWrapper).props().checked).toBeFalsy() + expect(getBulkEditCheckbox()).not.toHaveClass('indeterminate') + // expect( + // getBulkEditCheckbox(rtlWrapper).props().className.includes('indeterminate') + // ).toBeFalsy() + getRowBulkCheckboxInputs().forEach((checkbox) => { + expect(checkbox).not.toBeChecked() + }) + // expect(getRowBulkCheckboxInputs(rtlWrapper).map(isChecked)).toEqual( + // Array(6).fill(false) + // ) + const leaseUpApplicationsFilterContainer = screen.getByTestId( + 'lease-up-applications-filter-container' + ) expect( - getTopLevelInputWrapper(wrapper).props().className.includes('indeterminate') - ).toBeFalsy() - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(false)) - expect(findWithText(wrapper, Button, 'Set Status').props().disabled).toBeTruthy() + within(leaseUpApplicationsFilterContainer).getByRole('combobox') + ).toHaveAttribute('data-disabled', 'true') + // expect(findWithText(rtlWrapper, Button, 'Set Status').props().disabled).toBeTruthy() }) }) }) @@ -527,32 +661,32 @@ describe('LeaseUpApplicationsPage', () => { describe('when a checkbox that applies to multiple rows is clicked', () => { beforeEach(() => { act(() => { - getRowBulkCheckboxInputs(wrapper).last().simulate('click') + fireEvent.click(getRowBulkCheckboxInputs().pop()) + // getRowBulkCheckboxInputs(rtlWrapper).last().simulate('click') }) - wrapper.update() + // rtlWrapper.update() }) test('only the two affected rows are checked', () => { - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual([ - false, - false, - false, - false, - true, - true - ]) + const expectation = [false, false, false, false, true, true] + getRowBulkCheckboxInputs().forEach((checkbox, idx) => { + expect(checkbox).toHaveProperty('checked', expectation[idx]) + }) }) describe('when the same checkbox that applies to multiple rows is clicked again', () => { beforeEach(() => { act(() => { - getRowBulkCheckboxInputs(wrapper).last().simulate('click') + fireEvent.click(getRowBulkCheckboxInputs().pop()) }) - wrapper.update() + // rtlWrapper.update() }) test('all rows are unchecked', () => { - expect(getRowBulkCheckboxInputs(wrapper).map(isChecked)).toEqual(Array(6).fill(false)) + getRowBulkCheckboxInputs().forEach((checkbox, idx) => { + expect(checkbox).not.toBeChecked() + }) + // expect(getRowBulkCheckboxInputs(rtlWrapper).map(isChecked)).toEqual(Array(6).fill(false)) }) }) }) diff --git a/spec/javascript/components/lease_ups/LeaseUpListingsTable.test.js b/spec/javascript/components/lease_ups/LeaseUpListingsTable.test.js index 4850fbf5d..49c4a5269 100644 --- a/spec/javascript/components/lease_ups/LeaseUpListingsTable.test.js +++ b/spec/javascript/components/lease_ups/LeaseUpListingsTable.test.js @@ -1,6 +1,6 @@ import React from 'react' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import LeaseUpListingsTable from 'components/lease_ups/LeaseUpListingsTable' @@ -10,10 +10,10 @@ describe('LeaseUpListingsTable', () => { const listing = modelsFactory.listingDetail() test('should render succesfully', () => { - const wrapper = renderer.create( + const { asFragment } = render( ) - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) }) diff --git a/spec/javascript/components/lease_ups/__snapshots__/LeaseUpApplicationsPage.test.js.snap b/spec/javascript/components/lease_ups/__snapshots__/LeaseUpApplicationsPage.test.js.snap index 91fa693c7..ef25e8cf2 100644 --- a/spec/javascript/components/lease_ups/__snapshots__/LeaseUpApplicationsPage.test.js.snap +++ b/spec/javascript/components/lease_ups/__snapshots__/LeaseUpApplicationsPage.test.js.snap @@ -1,30104 +1,2976 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`LeaseUpApplicationsPage should match the snapshot 1`] = ` - - +
    - -
    + +
    +

    + listingName +

    +
    +

    + buildingAddress +

    +
    +
    + + + Export + + +
    +
    +
    +
    + +
    +
    +
    - -
    +
    +
    - - - -
    -

    + + + + + + +

    + +
    +
    +
    +
    - +
    + +
    +
    + +
    - - - +
    +
    -
    - - +
    +
    +
    +
    +
    +
    +
    + Rank +
    +
    +
    +
    +
    + Application +
    +
    +
    +
    +
    + First Name +
    +
    +
    +
    +
    + Last Name +
    +
    +
    +
    +
    + HH +
    +
    +
    +
    +
    + Requests +
    +
    +
    +
    +
    + Updated +
    +
    +
    +
    +
    + Latest Substatus +
    +
    +
    +
    +
    + Status +
    +
    +
    +
    +
    +
    +
    - - +
    + +
    +
    +
    - - -
    -
    -
    - - - -
    -
    -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - - - - -
    -
    -
    - - + V-COP 2 +
    - - - - + +
    +
    + some first name 1 +
    +
    +
    +
    + some last name 1 +
    +
    +
    +
    +
    + Vision +
    +
    +
    +
    + 04/26/2018 +
    +
    +
    +
    +
    +
    - -
    - -
    - -
    - -
    -
    - -
    - -
    - - -
    -
    - Rank -
    - -
    - -
    - - -
    -
    - Application -
    - -
    - -
    - - -
    -
    - First Name -
    - -
    - -
    - - -
    -
    - Last Name -
    - -
    - -
    - - -
    -
    - HH -
    - -
    - -
    - - -
    -
    - Requests -
    - -
    - -
    - - -
    -
    - Updated -
    - -
    - -
    - - -
    -
    - Latest Substatus -
    - -
    - -
    - - -
    -
    - Status -
    - -
    - -
    - -
    - -
    - - -
    - -
    - -
    - -
    - - -
    - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - V-COP 2 -
    -
    -
    -
    -
    -
    - - - - -
    - -
    - some first name 1 -
    -
    -
    -
    - -
    - -
    - some last name 1 -
    -
    -
    -
    - -
    - - -
    - -
    - Vision -
    -
    -
    -
    - -
    - - -
    - 04/26/2018 -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -
    - - -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - - - - - - -
    - - -
    - -
    - -
    - - -
    - -
    - -
    - - -
    - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - COP 1 -
    -
    -
    -
    -
    -
    - - - - -
    - -
    - some first name 2 -
    -
    -
    -
    - -
    - -
    - some last name 2 -
    -
    -
    -
    - -
    - - -
    - -
    - Vision -
    -
    -
    -
    - -
    - - -
    - 04/26/2018 -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -
    - - -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - - - - - - -
    - - -
    - -
    - -
    - - -
    - -
    - -
    - - -
    - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - V-COP 3 -
    -
    -
    -
    -
    -
    - - - - -
    - -
    - some first name 3 -
    -
    -
    -
    - -
    - -
    - some last name 3 -
    -
    -
    -
    - -
    - - -
    - -
    - Vision -
    -
    -
    -
    - -
    - - -
    - 04/26/2018 -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -
    - - -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - - - - - - -
    - - -
    - -
    - -
    - - -
    - -
    - -
    - - -
    - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - COP 2 -
    -
    -
    -
    -
    -
    - - - - -
    - -
    - some first name 4 -
    -
    -
    -
    - -
    - -
    - some last name 4 -
    -
    -
    -
    - -
    - - -
    - -
    - Vision -
    -
    -
    -
    - -
    - - -
    - 04/26/2018 -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -
    - - -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - - - - - - -
    - - -
    - -
    -
    -
    - - -
    - -
    - -
    - - -
    - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - COP 1 -
    -
    -
    -
    -
    -
    - - - - -
    - -
    - some first name 5 -
    -
    -
    -
    - -
    - -
    - some last name 5 -
    -
    -
    -
    - -
    - - -
    - -
    - Vision -
    -
    -
    -
    - -
    - - -
    - 04/26/2018 -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -
    - - -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - - - - - - -
    - - -
    - -
    - -
    - - -
    - -
    - -
    - - -
    - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - COP 1 -
    -
    -
    -
    -
    -
    - - - - -
    - -
    - some first name 6 -
    -
    -
    -
    - -
    - -
    - some last name 6 -
    -
    -
    -
    - -
    - - -
    - -
    - Vision -
    -
    -
    -
    - -
    - - -
    - 04/26/2018 -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -
    - - -
    - - - - - - -
    - - -
    - - - - - - - -
    - - - - -
    - - -
    - - - - - - - - - - - -
    - - -
    - -
    - -
    - - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    - -
    - - -   - - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - + -
    - + + + + + + +
    - - - +
    +
    +
    +
    - +
    + +
    +
    +
    +
    +
    + COP 1 +
    +
    +
    + +
    +
    + some first name 2 +
    +
    +
    +
    + some last name 2 +
    +
    +
    +
    +
    + Vision +
    +
    +
    - + 04/26/2018 +
    +
    +
    +
    +
    +
    +
    - + + + + + + + + +
    - - - + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + V-COP 3 +
    +
    +
    + +
    +
    + some first name 3 +
    +
    +
    +
    + some last name 3 +
    +
    +
    +
    +
    + Vision +
    +
    +
    +
    + 04/26/2018 +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + COP 2 +
    +
    +
    + +
    +
    + some first name 4 +
    +
    +
    +
    + some last name 4 +
    +
    +
    +
    +
    + Vision +
    +
    +
    +
    + 04/26/2018 +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + COP 1 +
    +
    +
    + +
    +
    + some first name 5 +
    +
    +
    +
    + some last name 5 +
    +
    +
    +
    +
    + Vision +
    +
    +
    +
    + 04/26/2018 +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + COP 1 +
    +
    +
    + +
    +
    + some first name 6 +
    +
    +
    +
    + some last name 6 +
    +
    +
    +
    +
    + Vision +
    +
    +
    +
    + 04/26/2018 +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + Page +
    + +
    + of + + 1 + +
    +
    +
    + +
    +
    +
    +
    +
    + Loading... +
    +
    +
    +
    +
    `; diff --git a/spec/javascript/components/lease_ups/__snapshots__/LeaseUpListingsTable.test.js.snap b/spec/javascript/components/lease_ups/__snapshots__/LeaseUpListingsTable.test.js.snap index 8d5b81c12..4f7425142 100644 --- a/spec/javascript/components/lease_ups/__snapshots__/LeaseUpListingsTable.test.js.snap +++ b/spec/javascript/components/lease_ups/__snapshots__/LeaseUpListingsTable.test.js.snap @@ -1,2777 +1,1650 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`LeaseUpListingsTable should render succesfully 1`] = ` -
    +
    -
    - Listing Name -
    -
    -
    -
    -
    - Lottery Date -
    -
    -
    -
    -
    - Lottery Results Date -
    -
    -
    -
    -
    - Applications in Lottery -
    -
    -
    -
    -
    - Total Units Available -
    -
    -
    -
    -
    - Leases Signed -
    -
    -
    -
    -
    - Last updated -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - Test 5/30 -
    -
    -
    - 03/22/2017 -
    -
    -
    -
    - 03/23/2017 -
    -
    -
    - 2 -
    -
    -
    -
    - - none - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    - -   - -
    -
    -
    -
    -
    - -   - +
    + Listing Name +
    +
    - -   - +
    + Lottery Date +
    +
    - -   - +
    + Lottery Results Date +
    +
    - -   - +
    + Applications in Lottery +
    +
    - -   - +
    + Total Units Available +
    +
    - -   - +
    + Leases Signed +
    +
    - -   - +
    + Last updated +
    +
    - -   - +
    - -   - -
    + class="rt-th text-right" + role="columnheader" + style="flex: 100 0 auto; width: 100px;" + tabindex="-1" + />
    - -   - -
    + class="rt-th text-right" + role="columnheader" + style="flex: 120 0 auto; width: 120px;" + tabindex="-1" + />
    - -   - -
    + class="rt-th text-right" + role="columnheader" + style="flex: 100 0 auto; width: 100px;" + tabindex="-1" + />
    - -   - -
    + class="rt-th text-right" + role="columnheader" + style="flex: 100 0 auto; width: 100px;" + tabindex="-1" + />
    - -   - -
    + class="rt-th text-right" + role="columnheader" + style="flex: 100 0 auto; width: 100px;" + tabindex="-1" + />
    - -   - -
    + class="rt-th text-right" + role="columnheader" + style="flex: 100 0 auto; width: 100px;" + tabindex="-1" + />
    - -   - -
    -
    - -   - +
    + Test 5/30 +
    +
    +
    + 03/22/2017 +
    +
    +
    +
    + 03/23/2017 +
    +
    +
    + 2 +
    +
    +
    +
    + + none + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    -
    -
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    -
    -
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -odd" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    +
    - -   - + class="rt-tr -padRow -even" + role="row" + > +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    +
    + +   + +
    -
    -
    - Loading... +
    + Loading... +
    -
    + `; diff --git a/spec/javascript/components/lease_ups/application_page/CheckboxCell.test.js b/spec/javascript/components/lease_ups/application_page/CheckboxCell.test.js index 88ac1f1bf..911ef1c00 100644 --- a/spec/javascript/components/lease_ups/application_page/CheckboxCell.test.js +++ b/spec/javascript/components/lease_ups/application_page/CheckboxCell.test.js @@ -1,47 +1,40 @@ import React from 'react' -import { shallow } from 'enzyme' +import { fireEvent, render, screen } from '@testing-library/react' -import Checkbox from 'components/atoms/Checkbox' import CheckboxCell from 'components/lease_ups/application_page/CheckboxCell' const mockOnClick = jest.fn() describe('CheckboxCell', () => { describe('when not checked', () => { - let wrapper beforeEach(() => { - wrapper = shallow( - - ) + render() }) test('the input is set to unchecked', () => { - expect(wrapper.find(Checkbox).props().checked).toBeFalsy() + expect(screen.getByRole('checkbox')).not.toBeChecked() }) test('onClick is trigged when the input changes', () => { expect(mockOnClick.mock.calls).toHaveLength(0) - wrapper.find(Checkbox).simulate('click') + fireEvent.click(screen.getByRole('checkbox')) expect(mockOnClick.mock.calls).toHaveLength(1) }) }) describe('when checked', () => { - let wrapper beforeEach(() => { - wrapper = shallow( - - ) + render() }) test('the input is set to unchecked', () => { - expect(wrapper.find(Checkbox).props().checked).toBeTruthy() + expect(screen.getByRole('checkbox')).toBeChecked() }) test('onClick is trigged when the input changes', () => { expect(mockOnClick.mock.calls).toHaveLength(0) - wrapper.find(Checkbox).simulate('click') + fireEvent.click(screen.getByRole('checkbox')) expect(mockOnClick.mock.calls).toHaveLength(1) }) }) diff --git a/spec/javascript/components/lease_ups/application_page/PreferenceRankCell.test.js b/spec/javascript/components/lease_ups/application_page/PreferenceRankCell.test.js index 58f1cf646..094a356e1 100644 --- a/spec/javascript/components/lease_ups/application_page/PreferenceRankCell.test.js +++ b/spec/javascript/components/lease_ups/application_page/PreferenceRankCell.test.js @@ -1,90 +1,88 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render, screen } from '@testing-library/react' -import { COLORS } from 'components/atoms/colors' -import StyledIcon from 'components/atoms/StyledIcon' import PreferenceRankCell, { VALIDATION_CONFIRMED, VALIDATION_INVALID, VALIDATION_UNCONFIRMED } from 'components/lease_ups/application_page/PreferenceRankCell' -import { findWithProps } from '../../../testUtils/wrapperUtil' - const MOCK_PREF_RANK = 'NRHP 2' describe('PreferenceRankCell', () => { describe('with Unconfirmed preference', () => { - let wrapper + let rtlWrapper beforeEach(() => { - wrapper = shallow( - + rtlWrapper = render( + ) }) test('renders the preference rank', () => { - expect(wrapper.text().includes(MOCK_PREF_RANK)).toBeTruthy() + expect(screen.getByText(MOCK_PREF_RANK)).toBeInTheDocument() }) test('does not render an icon', () => { - expect(wrapper.find(StyledIcon)).toHaveLength(0) + expect(screen.queryByTestId('preference-rank-x-icon')).not.toBeInTheDocument() + expect(screen.queryByTestId('preference-rank-check-icon')).not.toBeInTheDocument() }) test('renders cell with flex-start justification', () => { - expect(wrapper.props().style.justifyContent).toEqual('flex-start') + expect(rtlWrapper.asFragment()).toMatchSnapshot() }) }) describe('with Confirmed preference', () => { - let wrapper + let rtlWrapper beforeEach(() => { - wrapper = shallow( - + rtlWrapper = render( + ) }) test('renders the preference rank', () => { - expect(wrapper.text().includes(MOCK_PREF_RANK)).toBeTruthy() + expect(screen.getByText(MOCK_PREF_RANK)).toBeInTheDocument() }) test('renders a check icon', () => { - expect(wrapper.find(StyledIcon)).toHaveLength(1) - expect(findWithProps(wrapper, StyledIcon, { icon: 'check' })).toHaveLength(1) - }) - - test('renders the check with a green color', () => { - expect(findWithProps(wrapper, StyledIcon, { customFill: COLORS.success })).toHaveLength(1) + expect(screen.getByTestId('preference-rank-check-icon')).toBeInTheDocument() + expect(screen.queryByTestId('preference-rank-x-icon')).not.toBeInTheDocument() }) - test('renders cell with flex-start justification', () => { - expect(wrapper.props().style.justifyContent).toEqual('flex-start') + test('renders with the correct styling', () => { + expect(rtlWrapper.asFragment()).toMatchSnapshot() }) }) describe('with Invalid preference', () => { - let wrapper + let rtlWrapper beforeEach(() => { - wrapper = shallow( - + rtlWrapper = render( + ) }) test('renders the preference rank', () => { - expect(wrapper.text().includes(MOCK_PREF_RANK)).toBeTruthy() + expect(screen.getByText(MOCK_PREF_RANK)).toBeInTheDocument() }) test('renders a check icon', () => { - expect(wrapper.find(StyledIcon)).toHaveLength(1) - expect(findWithProps(wrapper, StyledIcon, { icon: 'close' })).toHaveLength(1) + expect(screen.queryByTestId('preference-rank-check-icon')).not.toBeInTheDocument() + expect(screen.getByTestId('preference-rank-x-icon')).toBeInTheDocument() }) - test('renders the check with a green color', () => { - expect(findWithProps(wrapper, StyledIcon, { customFill: COLORS.alert })).toHaveLength(1) - }) - - test('renders cell with flex-start justification', () => { - expect(wrapper.props().style.justifyContent).toEqual('flex-start') + test('renders with the correct styling', () => { + expect(rtlWrapper.asFragment()).toMatchSnapshot() }) }) }) diff --git a/spec/javascript/components/lease_ups/application_page/StatusCell.test.js b/spec/javascript/components/lease_ups/application_page/StatusCell.test.js index b3da4e4f6..8955db75d 100644 --- a/spec/javascript/components/lease_ups/application_page/StatusCell.test.js +++ b/spec/javascript/components/lease_ups/application_page/StatusCell.test.js @@ -1,33 +1,36 @@ import React from 'react' -import { shallow } from 'enzyme' +import { fireEvent, render, screen, act } from '@testing-library/react' +import selectEvent from 'react-select-event' import StatusCell from 'components/lease_ups/application_page/StatusCell' -import StatusDropdown from 'components/molecules/StatusDropdown' -import StatusHistoryPopover from 'components/organisms/StatusHistoryPopover' const mockOnChange = jest.fn() describe('StatusCell', () => { - let wrapper + let rtlWrapper beforeEach(() => { - wrapper = shallow( + rtlWrapper = render( ) }) test('renders an a dropdown and a popover', () => { - expect(wrapper.find(StatusDropdown)).toHaveLength(1) - expect(wrapper.find(StatusHistoryPopover)).toHaveLength(1) + // Find the dropdown + expect(screen.getByRole('combobox')).toBeInTheDocument() + + // Both the dropdown and popover have buttons, there should be two + expect(screen.queryAllByRole('button')).toHaveLength(2) }) - test('renders the popover button with left margin', () => { - expect(wrapper.find(StatusHistoryPopover).props().customButtonClasses).toEqual('margin-left') + test('renders with the correct styling', () => { + expect(rtlWrapper.asFragment()).toMatchSnapshot() }) test('onClick is trigged when the input changes', () => { + selectEvent.openMenu(screen.getByRole('combobox')) expect(mockOnChange.mock.calls).toHaveLength(0) - wrapper.find(StatusDropdown).simulate('change') + act(() => fireEvent.click(screen.getByText(/disqualified/i))) expect(mockOnChange.mock.calls).toHaveLength(1) }) }) diff --git a/spec/javascript/components/lease_ups/application_page/__snapshots__/PreferenceRankCell.test.js.snap b/spec/javascript/components/lease_ups/application_page/__snapshots__/PreferenceRankCell.test.js.snap new file mode 100644 index 000000000..8fab2f8ba --- /dev/null +++ b/spec/javascript/components/lease_ups/application_page/__snapshots__/PreferenceRankCell.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PreferenceRankCell with Confirmed preference renders with the correct styling 1`] = ` + +
    +
    + NRHP 2 +
    + + + +
    +
    +`; + +exports[`PreferenceRankCell with Invalid preference renders with the correct styling 1`] = ` + +
    +
    + NRHP 2 +
    + + + +
    +
    +`; + +exports[`PreferenceRankCell with Unconfirmed preference renders cell with flex-start justification 1`] = ` + +
    +
    + NRHP 2 +
    +
    +
    +`; diff --git a/spec/javascript/components/lease_ups/application_page/__snapshots__/StatusCell.test.js.snap b/spec/javascript/components/lease_ups/application_page/__snapshots__/StatusCell.test.js.snap new file mode 100644 index 000000000..ccc1b19e6 --- /dev/null +++ b/spec/javascript/components/lease_ups/application_page/__snapshots__/StatusCell.test.js.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StatusCell renders with the correct styling 1`] = ` + +
    + + +
    +
    +`; diff --git a/spec/javascript/components/listing/ListingPage.test.js b/spec/javascript/components/listing/ListingPage.test.js index 6e12342a8..dcba3386e 100644 --- a/spec/javascript/components/listing/ListingPage.test.js +++ b/spec/javascript/components/listing/ListingPage.test.js @@ -1,7 +1,6 @@ import React from 'react' -import _ from 'lodash' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import { detailsFields, @@ -18,35 +17,32 @@ import ListingPage from 'components/listings/ListingPage' import modelsFactory from '../../factories/models' +const testCases = [ + { title: 'Details', fields: detailsFields }, + { title: 'Building Information', fields: buildingInformationFields }, + { title: 'Accessibility, Amenities, Fees', fields: aafFields }, + { title: 'Lottery Information', fields: lotteryInfoFields }, + { title: 'Application Information', fields: appInfoFields }, + { title: 'Leasing Agent and Developer Information', fields: agentDevInfoFields }, + { title: 'Additional Eligibility Rules', fields: eligibilityRulesFields }, + { title: 'Additional Information', fields: additionalInfoFields } +] + describe('ListingPage', () => { const listing = modelsFactory.listingDetail() test('should render succesfully', () => { - const wrapper = renderer.create() + const { asFragment } = render() - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) describe('individual fields', () => { - _.each( - [ - { title: 'Details', fields: detailsFields }, - { title: 'Building Information', fields: buildingInformationFields }, - { title: 'Accessibility, Amenities, Fees', fields: aafFields }, - { title: 'Lottery Information', fields: lotteryInfoFields }, - { title: 'Application Information', fields: appInfoFields }, - { title: 'Leasing Agent and Developer Information', fields: agentDevInfoFields }, - { title: 'Additional Eligibility Rules', fields: eligibilityRulesFields }, - { title: 'Additional Information', fields: additionalInfoFields } - ], - ({ title, fields }) => { - test(`${title} fields`, () => { - const wrapper = renderer.create( - - ) - expect(wrapper.toJSON()).toMatchSnapshot() - }) - } - ) + test.each(testCases)('%s fields', ({ title, fields }) => { + const { asFragment } = render( + + ) + expect(asFragment()).toMatchSnapshot() + }) }) }) diff --git a/spec/javascript/components/listing/ListingsPage.test.js b/spec/javascript/components/listing/ListingsPage.test.js index 1235e5ac0..ce861bc84 100644 --- a/spec/javascript/components/listing/ListingsPage.test.js +++ b/spec/javascript/components/listing/ListingsPage.test.js @@ -1,6 +1,6 @@ import React from 'react' -import renderer from 'react-test-renderer' +import { render } from '@testing-library/react' import ListingsPage from 'components/listings/ListingsPage' @@ -12,15 +12,15 @@ describe('ListingsPage', () => { const results = modelsFactory.listingsList() const fields = modelsFactory.listingFields() - const wrapper = renderer.create() + const { asFragment } = render() - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) test('should render succesfully long list', () => { const fields = modelsFactory.listingFields() - const wrapper = renderer.create() + const { asFragment } = render() - expect(wrapper.toJSON()).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) }) diff --git a/spec/javascript/components/listing/__snapshots__/ListingPage.test.js.snap b/spec/javascript/components/listing/__snapshots__/ListingPage.test.js.snap index 3417e0704..625cb5a9c 100644 --- a/spec/javascript/components/listing/__snapshots__/ListingPage.test.js.snap +++ b/spec/javascript/components/listing/__snapshots__/ListingPage.test.js.snap @@ -1,135 +1,138 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ListingPage individual fields Accessibility, Amenities, Fees fields 1`] = ` -
    -

    - Accessibility, Amenities, Fees -

    -
      +
      -
    • -
      +
        +
      • -

        - Amenities -

        -

        - Laundry room, underground parking, courtyard, bike room -

        +

        + Amenities +

        +

        + Laundry room, underground parking, courtyard, bike room +

        +
      -
    • - -
    • -
      +
    • -

      - Deposit Min -

      -

      - $2,102.00 -

      -
      -
      -

      + Deposit Min +

      +

      + $2,102.00 +

      +
      +
      - Deposit Max - -

      - $2,355.00 -

      -
      -
      -

      + Deposit Max +

      +

      + $2,355.00 +

      +
      +
      - Costs Not Included - -

      - Tenants pay for gas, electricity. +

      + Costs Not Included +

      +

      + Tenants pay for gas, electricity. For pet fees: Cat is allowed with a $500 refundable deposit, $250 non-refundable cleaning fee and a pet addendum. Dogs are not allowed in the building. One parking space per unit available for $175 a month. -

      +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Additional Eligibility Rules fields 1`] = ` -
    -

    - Additional Eligibility Rules -

    -
      +
      -
    • -
      +
        +
      • - +
        - Eviction History - -

        - Provide minimum of 4 years rental history with at least two prior rentals in which you were responsible for paying the rent. Applicants without rental history will still be considered. No Guarantors permitted. +

        + Eviction History +

        +

        + Provide minimum of 4 years rental history with at least two prior rentals in which you were responsible for paying the rent. Applicants without rental history will still be considered. No Guarantors permitted. Previous rental history will be reviewed and must exhibit no derogatory references. @@ -138,26 +141,26 @@ Landlord references will only check for evictions, payment history, and document All debt owed to an apartment community must be satisfied. Mitigating circumstances may be considered. -

        +

        +
      -
    • - -
    • -
      +
    • -

      - Credit Rating -

      -

      - Provide a credit report with score from Equifax, Experian, or TransUnion dated within thirty (30) days of the application. +

      + Credit Rating +

      +

      + Provide a credit report with score from Equifax, Experian, or TransUnion dated within thirty (30) days of the application. Accounts that are not current or that are derogatory will negatively affect the overall scoring, which could result in the denial of the application or an additional deposit may be required. @@ -168,723 +171,729 @@ Bankruptcy if not cleared will be an automatic denial of the rental application. No Guarantors permitted. Mitigating circumstances may be considered. -

      +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Additional Information fields 1`] = ` -
    -

    - Additional Information -

    -
      +
      -
    • -
      +
        +
      • -

        - Required Documents -

        -

        - Lottery winners will be required to fill out a building application and provide a copy of your current credit report, 3 most recent paystubs, current tax returns and W-2, and 3 most recent bank statements. -

        -
        -
        -

        + Required Documents +

        +

        + Lottery winners will be required to fill out a building application and provide a copy of your current credit report, 3 most recent paystubs, current tax returns and W-2, and 3 most recent bank statements. +

        +
        +
        - Smoking Policy - -

        - Non-smoking building -

        +

        + Smoking Policy +

        +

        + Non-smoking building +

        +
      -
    • - -
    • -
      +
    • -

      - Legal Disclaimers -

      -

      -

      -
      -

      + Legal Disclaimers +

      +

      + some disclaimer +

      +
      +
      - Pet Policy - -

      - Dogs are not allowed in the building. -

      +

      + Pet Policy +

      +

      + Dogs are not allowed in the building. +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Application Information fields 1`] = ` -
    -

    - Application Information -

    -
      +
      -
    • -
      +
        +
      • -

        - Application Phone -

        -

        - (415) 227-2256 -

        -
        -
        -

        + Application Phone +

        +

        + (415) 227-2256 +

        +
        +
        - Application Organization - -

        - 280 Fell-BMR -

        -
        -
        -

        + Application Organization +

        +

        + 280 Fell-BMR +

        +
        +
        - Application Street Address - -

        - P.O. Box 420847 -

        +

        + Application Street Address +

        +

        + P.O. Box 420847 +

        +
      -
    • - -
    • -
      +
    • -

      - Application City -

      -

      - San Francisco -

      -
      -
      -

      + Application City +

      +

      + San Francisco +

      +
      +
      - Application State - -

      - CA -

      -
      -
      -

      + Application State +

      +

      + CA +

      +
      +
      - Application Postal Code - -

      - 94142 -

      +

      + Application Postal Code +

      +

      + 94142 +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Building Information fields 1`] = ` -
    -

    - Building Information -

    -
      +
      -
    • -
      +
        +
      • -

        - Building Name -

        -

        - 77 Bluxome -

        -
        -
        -

        + Building Name +

        +

        + 77 Bluxome +

        +
        +
        - Building Street Address - -

        - 77 Bluxome Street -

        -
        -
        -

        + Building Street Address +

        +

        + 77 Bluxome Street +

        +
        +
        - Building City - -

        - San Francisco -

        -
        -
        -

        + Building City +

        +

        + San Francisco +

        +
        +
        - Building State - -

        - CA -

        -
        -
        -

        + Building State +

        +

        + CA +

        +
        +
        - Building Zip Code - -

        - 94107 -

        +

        + Building Zip Code +

        +

        + 94107 +

        +
      -
    • - -
    • -
      +
    • -

      - Neighborhood -

      -

      - South of Market -

      -
      -
      -

      - Developer -

      -

      - Equity Residential -

      -
      -
      -

      + Neighborhood +

      +

      + South of Market +

      +
      + + - +
      - Year Built - -

      - 2008 -

      +

      + Year Built +

      +

      + 2008 +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Details fields 1`] = ` -
    -

    - Details -

    -
      +
      -
    • -
      +
        +
      • -

        - Owner -

        -

        - Prod Vertiba -

        -
        -
        -

        + Owner +

        +

        + Prod Vertiba +

        +
        +
        - Name - -

        - Test 5/30 -

        -
        -
        -

        + Name +

        +

        + Test 5/30 +

        +
        +
        - Account - -

        - Exygy -

        -
        -
        -

        + Account +

        +

        + Exygy +

        +
        +
        - Application Due Date - -

        - 05/31/2018 -

        +

        + Application Due Date +

        +

        + 05/31/2018 +

        +
      -
    • - -
    • -
      +
    • -

      - Applications in Lottery -

      -

      - 2 -

      -
      -
      -

      + Applications in Lottery +

      +

      + 2 +

      +
      +
      - Status - -

      - Active -

      +

      + Status +

      +

      + Active +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Leasing Agent and Developer Information fields 1`] = ` -
    -

    - Leasing Agent and Developer Information -

    -
      +
      -
    • -
      +
        +
      • -

        - Leasing Agent Name -

        -

        - Cullen McCaffrey -

        -
        -
        -

        + Leasing Agent Name +

        +

        + Cullen McCaffrey +

        +
        +
        - Leasing Agent Title - -

        - Sales Agent -

        +

        + Leasing Agent Title +

        +

        + Sales Agent +

        +
      -
    • - -
    • -
      +
    • -

      - Leasing Agent Email -

      -

      - 77bluxome@eqr.com -

      -
      -
      -

      + Leasing Agent Email +

      +

      + 77bluxome@eqr.com +

      +
      +
      - Leasing Agent Phone - -

      - (415) 957-5887 -

      +

      + Leasing Agent Phone +

      +

      + (415) 957-5887 +

      +
    -
    - - -
    + + +
    + `; -exports[`ListingPage individual fields Lottery Information fields 1`] = ` -
    -

    - Lottery Information -

    -
      +
      -
    • -
      +
        +
      • -

        - Lottery Date -

        -

        - 03/22/2017 -

        -
        -
        -

        + Lottery Date +

        +

        + 03/22/2017 +

        +
        +
        - Lottery Results Date - -

        - 03/23/2017 -

        -
        -
        -

        + Lottery Results Date +

        +

        + 03/23/2017 +

        +
        +
        - Lottery Venue - -

        - MOHCD -

        +

        + Lottery Venue +

        +

        + MOHCD +

        +
      -
    • - -
    • -
      +
    • -

      - Lottery Status -

      -

      - Not Yet Run -

      -
      -
      -

      + Lottery Status +

      +

      + Not Yet Run +

      +
      +
      - Lottery Street Address - -

      - 1 S. Van Ness Avenue 5th FL -

      -
      -
      -

      + Lottery Street Address +

      +

      + 1 S. Van Ness Avenue 5th FL +

      +
      +
      - Lottery City - -

      - San Francisco -

      +

      + Lottery City +

      +

      + San Francisco +

      +
    -
    - - -
    + + +
    + `; exports[`ListingPage should render succesfully 1`] = ` -[ +

    Test 5/30

    -
    , + , +

    Details

    • Owner

      @@ -893,10 +902,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Name

      @@ -905,10 +914,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Account

      @@ -917,10 +926,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Application Due Date

      @@ -931,16 +940,16 @@ exports[`ListingPage should render succesfully 1`] = `
    • Applications in Lottery

      @@ -949,10 +958,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Status

      @@ -965,27 +974,27 @@ exports[`ListingPage should render succesfully 1`] = `

    Building Information

    • Building Name

      @@ -994,10 +1003,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Building Street Address

      @@ -1006,10 +1015,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Building City

      @@ -1018,10 +1027,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Building State

      @@ -1030,10 +1039,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Building Zip Code

      @@ -1044,16 +1053,16 @@ exports[`ListingPage should render succesfully 1`] = `
    • Neighborhood

      @@ -1062,10 +1071,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Developer

      @@ -1074,10 +1083,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Building URL

      @@ -1090,10 +1099,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Year Built

      @@ -1106,10 +1115,10 @@ exports[`ListingPage should render succesfully 1`] = `

    Listing Preferences

    @@ -1151,27 +1160,27 @@ exports[`ListingPage should render succesfully 1`] = `

    Accessibility, Amenities, Fees

    • Amenities

      @@ -1182,16 +1191,16 @@ exports[`ListingPage should render succesfully 1`] = `
    • Deposit Min

      @@ -1200,10 +1209,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Deposit Max

      @@ -1212,10 +1221,10 @@ exports[`ListingPage should render succesfully 1`] = `

      Costs Not Included

      @@ -1234,27 +1243,27 @@ One parking space per unit available for $175 a month.

    Lottery Information

    • Lottery Date

      @@ -1263,10 +1272,10 @@ One parking space per unit available for $175 a month.

      Lottery Results Date

      @@ -1275,10 +1284,10 @@ One parking space per unit available for $175 a month.

      Lottery Venue

      @@ -1289,16 +1298,16 @@ One parking space per unit available for $175 a month.
    • Lottery Status

      @@ -1307,10 +1316,10 @@ One parking space per unit available for $175 a month.

      Lottery Street Address

      @@ -1319,10 +1328,10 @@ One parking space per unit available for $175 a month.

      Lottery City

      @@ -1335,27 +1344,27 @@ One parking space per unit available for $175 a month.

    Application Information

    • Application Phone

      @@ -1364,10 +1373,10 @@ One parking space per unit available for $175 a month.

      Application Organization

      @@ -1376,10 +1385,10 @@ One parking space per unit available for $175 a month.

      Application Street Address

      @@ -1390,16 +1399,16 @@ One parking space per unit available for $175 a month.
    • Application City

      @@ -1408,10 +1417,10 @@ One parking space per unit available for $175 a month.

      Application State

      @@ -1420,10 +1429,10 @@ One parking space per unit available for $175 a month.

      Application Postal Code

      @@ -1436,27 +1445,27 @@ One parking space per unit available for $175 a month.

    Leasing Agent and Developer Information

    • Leasing Agent Name

      @@ -1465,10 +1474,10 @@ One parking space per unit available for $175 a month.

      Leasing Agent Title

      @@ -1479,16 +1488,16 @@ One parking space per unit available for $175 a month.
    • Leasing Agent Email

      @@ -1497,10 +1506,10 @@ One parking space per unit available for $175 a month.

      Leasing Agent Phone

      @@ -1513,27 +1522,27 @@ One parking space per unit available for $175 a month.

    Additional Eligibility Rules

    • Building Selection Criteria

      @@ -1546,10 +1555,10 @@ One parking space per unit available for $175 a month.

      Eviction History

      @@ -1568,16 +1577,16 @@ Mitigating circumstances may be considered.
    • Credit Rating

      @@ -1600,27 +1609,27 @@ Mitigating circumstances may be considered.

    Additional Information

    • Required Documents

      @@ -1629,10 +1638,10 @@ Mitigating circumstances may be considered.

      Smoking Policy

      @@ -1643,32 +1652,28 @@ Mitigating circumstances may be considered.
    • Legal Disclaimers

      -

      +

      + some disclaimer +

      Pet Policy

      @@ -1681,10 +1686,10 @@ Mitigating circumstances may be considered.

    Open Houses

    @@ -1718,6 +1723,6 @@ Mitigating circumstances may be considered.
    -
    , -] + +
    `; diff --git a/spec/javascript/components/listing/__snapshots__/ListingsPage.test.js.snap b/spec/javascript/components/listing/__snapshots__/ListingsPage.test.js.snap index ca0a32517..59eb01cab 100644 --- a/spec/javascript/components/listing/__snapshots__/ListingsPage.test.js.snap +++ b/spec/javascript/components/listing/__snapshots__/ListingsPage.test.js.snap @@ -1,284 +1,185 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ListingsPage should render succesfully 1`] = ` -[ +

    Listings

    -
    , +
    Listing Name
    Application Due Date
    Lottery Date
    Lottery Results Date
    Lottery Status
    Flagged Applications
    Applications In Lottery
    - - of - + of 1
    Test 5/30
    05/31/2018
    Not Yet Run
    0
    2
    test
    05/29/2018
    Not Yet Run
    0
    1
    test abc
    06/05/2018
    Not Yet Run
    0
    Test listing 6/4
    Not Yet Run
    0
    Newer 1036 Mission
    07/03/2018
    08/17/2018
    08/24/2018
    Not Yet Run
    0
    0
    Krissy New Listing 4/5
    04/12/2018
    Not Yet Run
    0
    0
    Krissy Test 411
    06/28/2018
    Lottery Complete
    3
    7
    Krissy Test 614
    06/20/2018
    06/16/2018
    Lottery Complete
    0
    15
    Alice Griffith Test Pref
    06/23/2018
    07/18/2018
    07/20/2018
    Not Yet Run
    0
    3
    test 44
    Not Yet Run
    0
    test
    Not Yet Run
    0
    PreAndPost Lottery Validations
    07/16/2018
    08/18/2018
    08/18/2018
    Not Yet Run
    0
    1
    test
    06/18/2018
    Not Yet Run
    0
    testshare
    Not Yet Run
    0
    626 Mission Bay Family Housing MANUAL
    06/08/2018
    06/12/2018
    06/19/2018
    Not Yet Run
    0
    0
    Test Listing
    Lottery Complete
    0
    4
    Automated Test Listing Senior All (please do not modify)
    01/01/2021
    01/22/2021
    03/23/2017
    Not Yet Run
    0
    0
    Krissy 4/13
    07/12/2018
    Lottery Complete
    0
    2
    test 2
    06/01/2018
    Not Yet Run
    0
    0
    test
    Not Yet Run
    4
    5
    @@ -5903,87 +3419,78 @@ exports[`ListingsPage should render succesfully long list 1`] = `
    - Page - + Page
    - - of - + of 9
    +
    + +
    +
    + +`; + +exports[`Dropdown it renders with default props correctly 1`] = ` + + + +`; diff --git a/spec/javascript/components/molecules/__snapshots__/FormGrid.test.js.snap b/spec/javascript/components/molecules/__snapshots__/FormGrid.test.js.snap new file mode 100644 index 000000000..a4d543087 --- /dev/null +++ b/spec/javascript/components/molecules/__snapshots__/FormGrid.test.js.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FormGrid.Item renders the FormGrid.Item properly with default props 1`] = ` + +
    + ABC +
    +
    +`; + +exports[`FormGrid.Item renders the FormGrid.Item properly with width=25% 1`] = ` + +
    + ABC +
    +
    +`; + +exports[`FormGrid.Item renders the FormGrid.Item properly with width=33% 1`] = ` + +
    + ABC +
    +
    +`; diff --git a/spec/javascript/components/molecules/__snapshots__/InlineModal.test.js.snap b/spec/javascript/components/molecules/__snapshots__/InlineModal.test.js.snap index 7d2d64cc8..fadc98719 100644 --- a/spec/javascript/components/molecules/__snapshots__/InlineModal.test.js.snap +++ b/spec/javascript/components/molecules/__snapshots__/InlineModal.test.js.snap @@ -1,13 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`InlineModal should pass the white background prop if specified 1`] = ` -
    + +
    + `; exports[`InlineModal should use the inline-modal class for background if background is not specified 1`] = ` -
    + +
    + `; diff --git a/spec/javascript/components/molecules/__snapshots__/MultiSelect.test.js.snap b/spec/javascript/components/molecules/__snapshots__/MultiSelect.test.js.snap new file mode 100644 index 000000000..a4dbc3ffe --- /dev/null +++ b/spec/javascript/components/molecules/__snapshots__/MultiSelect.test.js.snap @@ -0,0 +1,169 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MultiSelect when disabled renders as disabled 1`] = ` + +
    +
    + + +
    +
    +
    + Select... +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +`; + +exports[`MultiSelect with default height sets height to 45px 1`] = ` + +
    +
    + + +
    +
    +
    + Select... +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +`; diff --git a/spec/javascript/components/molecules/__snapshots__/Popover.test.js.snap b/spec/javascript/components/molecules/__snapshots__/Popover.test.js.snap index 5ac10d701..7e1841535 100644 --- a/spec/javascript/components/molecules/__snapshots__/Popover.test.js.snap +++ b/spec/javascript/components/molecules/__snapshots__/Popover.test.js.snap @@ -1,43 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Popover should renders the provided button as expected 1`] = ` - - - + `; exports[`Popover should renders the tooltip content as expected 1`] = ` - -

    popover content

    - + `; diff --git a/spec/javascript/components/molecules/__snapshots__/ShowHideFiltersButton.test.js.snap b/spec/javascript/components/molecules/__snapshots__/ShowHideFiltersButton.test.js.snap new file mode 100644 index 000000000..f35fafc51 --- /dev/null +++ b/spec/javascript/components/molecules/__snapshots__/ShowHideFiltersButton.test.js.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ShowHideFiltersButton it renders properly when numFiltersApplied=4 1`] = ` + + + +`; diff --git a/spec/javascript/components/molecules/__snapshots__/StatusDropdown.test.js.snap b/spec/javascript/components/molecules/__snapshots__/StatusDropdown.test.js.snap new file mode 100644 index 000000000..dcff9fbb8 --- /dev/null +++ b/spec/javascript/components/molecules/__snapshots__/StatusDropdown.test.js.snap @@ -0,0 +1,371 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StatusDropdown toggle renders as expected when value is null 1`] = ` + + + +`; + +exports[`StatusDropdown toggle renders with additional styles when provided 1`] = ` + + + +`; + +exports[`StatusDropdown toggle renders with additional styles when provided 2`] = ` + + + +`; + +exports[`StatusDropdown toggle renders with left-aligned text 1`] = ` + + + +`; + +exports[`StatusDropdown toggle renders without any additional button styles when none are provided 1`] = ` + + + +`; diff --git a/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebar.test.js b/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebar.test.js index 504daf7ba..fe162f57b 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebar.test.js +++ b/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebar.test.js @@ -1,78 +1,110 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render, screen, within } from '@testing-library/react' import LeaseUpSidebar from 'components/molecules/lease_up_sidebar/LeaseUpSidebar' -import LeaseUpStatusButtons from 'components/molecules/lease_up_sidebar/LeaseUpStatusButtons' -import StatusHistoryContainer from 'components/molecules/lease_up_sidebar/StatusHistoryContainer' import { mockStatusItem, mockStatusItems, mockManyStatusItems } from '../../../mocks/statusItemMock' -const getWrapper = (items, isLoading = false) => - shallow() +const getScreen = (items, isLoading = false) => + render() + +jest.mock('react-select', () => (props) => { + const handleChange = (event) => { + const option = props.options.find((option) => option.value === event.currentTarget.value) + props.onChange(option) + } + return ( + + ) +}) describe('LeaseUpSidebar', () => { describe('when not loading', () => { test('should render with empty status items correctly', () => { - const wrapper = getWrapper([]) + getScreen([]) // two sidebar buttons components, one for mobile, one for desktop - expect(wrapper.find(LeaseUpStatusButtons)).toHaveLength(2) - expect(wrapper.find(LeaseUpStatusButtons).first().prop('status')).toBeNull() - expect(wrapper.find(StatusHistoryContainer)).toHaveLength(0) + expect(screen.getAllByTestId('lease-up-status-buttons')).toHaveLength(2) + expect( + within(screen.getAllByTestId('lease-up-status-buttons')[0]).getByRole('combobox') + ).toHaveValue('') + expect(screen.queryByText('Status History')).not.toBeInTheDocument() }) test('should render with a single status item correctly', () => { - const wrapper = getWrapper([mockStatusItem()]) - expect(wrapper.find(LeaseUpStatusButtons)).toHaveLength(2) - expect(wrapper.find(LeaseUpStatusButtons).first().prop('status')).toEqual('Approved') - expect(wrapper.find(LeaseUpStatusButtons).first().prop('isLoading')).toBeFalsy() - expect(wrapper.find(StatusHistoryContainer).prop('statusItems')).toHaveLength(1) + getScreen([mockStatusItem()]) + expect(screen.getAllByTestId('lease-up-status-buttons')).toHaveLength(2) + expect( + within(screen.getAllByTestId('lease-up-status-buttons')[0]).getByRole('combobox') + ).toHaveValue('Approved') + expect(screen.getByRole('heading', { name: /status history/i, level: 2 })).toBeInTheDocument() }) test('should render with multiple status items correctly', () => { - const wrapper = getWrapper(mockStatusItems()) - expect(wrapper.find(LeaseUpStatusButtons)).toHaveLength(2) - expect(wrapper.find(LeaseUpStatusButtons).first().prop('status')).toEqual('Approved') - expect(wrapper.find(StatusHistoryContainer).prop('statusItems')).toHaveLength(6) + getScreen(mockStatusItems()) + expect(screen.getAllByTestId('lease-up-status-buttons')).toHaveLength(2) + expect( + within(screen.getAllByTestId('lease-up-status-buttons')[0]).getByRole('combobox') + ).toHaveValue('Approved') + // StatusHistoryContainer caps the amount of statuses able to be shown at 4, even though there are 6 mocks + expect(screen.getAllByTestId('status-item')).toHaveLength(4) }) test('should render with more than 4 status items correctly', () => { - const wrapper = getWrapper(mockManyStatusItems(5)) - expect(wrapper.find(LeaseUpStatusButtons)).toHaveLength(2) - expect(wrapper.find(LeaseUpStatusButtons).first().prop('status')).toEqual('Approved') - expect(wrapper.find(StatusHistoryContainer).prop('statusItems')).toHaveLength(5) + getScreen(mockManyStatusItems(5)) + expect(screen.getAllByTestId('lease-up-status-buttons')).toHaveLength(2) + expect( + within(screen.getAllByTestId('lease-up-status-buttons')[0]).getByRole('combobox') + ).toHaveValue('Approved') + // StatusHistoryContainer caps the amount of statuses able to be shown at 4, even though there are 6 mocks + expect(screen.getAllByTestId('status-item')).toHaveLength(4) }) test('should render with lease signed status', () => { - const wrapper = getWrapper([ + getScreen([ mockStatusItem({ status: 'Lease Signed', substatus: null }), mockStatusItem() ]) - - expect(wrapper.find(LeaseUpStatusButtons)).toHaveLength(2) - expect(wrapper.find(LeaseUpStatusButtons).first().prop('status')).toEqual('Lease Signed') + expect(screen.getAllByTestId('lease-up-status-buttons')).toHaveLength(2) + expect( + within(screen.getAllByTestId('lease-up-status-buttons')[0]).getByRole('combobox') + ).toHaveValue('Lease Signed') }) }) describe('when loading', () => { test('should set the buttons to be loading/disabled', () => { - const wrapper = getWrapper([mockStatusItem()], true) - - const statusButtonsWrapper = wrapper.find(LeaseUpStatusButtons).first() - const saveButtonWrapper = wrapper.find('button#save-supplemental-application').first() + getScreen([mockStatusItem()], true) - expect(statusButtonsWrapper.prop('isLoading')).toBeTruthy() - expect(saveButtonWrapper.prop('disabled')).toBeTruthy() + // When the buttons are loading, the data-disabled attribute is set to true + expect( + within(screen.getAllByTestId('lease-up-status-buttons')[0]).getByRole('combobox') + ).toHaveAttribute('data-disabled', 'true') + expect(screen.getAllByRole('button', { name: /saving…/i })[0]).toBeDisabled() }) }) describe('snapshot tests', () => { test('should match snapshot with 5 status items', () => { - const wrapper = getWrapper(mockManyStatusItems(5)) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockManyStatusItems(5)) + expect(asFragment()).toMatchSnapshot() }) }) }) diff --git a/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebarButtons.test.js b/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebarButtons.test.js index 0f594e3ae..8b8252319 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebarButtons.test.js +++ b/spec/javascript/components/molecules/lease_up_sidebar/LeaseUpSidebarButtons.test.js @@ -1,41 +1,58 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render, screen } from '@testing-library/react' import LeaseUpStatusButtons from 'components/molecules/lease_up_sidebar/LeaseUpStatusButtons' -import StatusDropdown from 'components/molecules/StatusDropdown' const ON_CHANGE_STATUS = jest.fn() +jest.mock('react-select', () => (props) => { + const handleChange = (event) => { + const option = props.options.find((option) => option.value === event.currentTarget.value) + props.onChange(option) + } + return ( + + ) +}) + describe('LeaseUpStatusButtons', () => { test('should render the status dropdown correctly', () => { - const wrapper = shallow( - - ) - expect(wrapper.find(StatusDropdown)).toHaveLength(1) - expect(wrapper.find(StatusDropdown).prop('status')).toEqual('Approved') + render() + expect(screen.getByRole('combobox')).toBeInTheDocument() + expect(screen.getByRole('combobox')).toHaveValue('Approved') }) test('should render the status dropdown correctly when no status is passed', () => { - const wrapper = shallow() - expect(wrapper.find(StatusDropdown)).toHaveLength(1) - expect(wrapper.find(StatusDropdown).prop('status')).toBeNull() + render() + expect(screen.getByRole('combobox')).toBeInTheDocument() + expect(screen.getByRole('combobox')).toHaveValue('') }) test('should render a comment button', () => { - const wrapper = shallow( - - ) - expect(wrapper.find('button#add-status-history-comment')).toHaveLength(1) - expect(wrapper.find('button#add-status-history-comment').prop('disabled')).toBeFalsy() + render() + expect(screen.getByRole('button')).toBeInTheDocument() + expect(screen.getByRole('button')).toBeEnabled() }) test('should disable both buttons when loading', () => { - const wrapper = shallow( - - ) + render() - expect(wrapper.find('button#add-status-history-comment').prop('disabled')).toBeTruthy() - expect(wrapper.find(StatusDropdown).prop('disabled')).toBeTruthy() + expect(screen.getByRole('button')).toBeDisabled() + expect(screen.getByRole('combobox')).toHaveAttribute('data-disabled', 'true') }) }) diff --git a/spec/javascript/components/molecules/lease_up_sidebar/StatusHistoryContainer.test.js b/spec/javascript/components/molecules/lease_up_sidebar/StatusHistoryContainer.test.js index f4e0782f8..d77e1e4fe 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/StatusHistoryContainer.test.js +++ b/spec/javascript/components/molecules/lease_up_sidebar/StatusHistoryContainer.test.js @@ -1,73 +1,70 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render, screen, fireEvent } from '@testing-library/react' import StatusHistoryContainer from 'components/molecules/lease_up_sidebar/StatusHistoryContainer' -import StatusItems from 'components/molecules/lease_up_sidebar/StatusItems' import { mockManyStatusItems, mockStatusItem } from '../../../mocks/statusItemMock' -const getWrapper = (items) => shallow() +const getScreen = (items) => render() describe('StatusHistoryContainer', () => { test('should render with empty status items correctly', () => { - const wrapper = getWrapper([]) - expect(wrapper.find(StatusItems)).toHaveLength(1) - expect(wrapper.find(StatusItems).prop('statusItems')).toHaveLength(0) + getScreen([]) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) test('should render with a single status item correctly', () => { - const wrapper = getWrapper([mockStatusItem()]) - expect(wrapper.find(StatusItems)).toHaveLength(1) - expect(wrapper.find(StatusItems).prop('statusItems')).toHaveLength(1) + getScreen([mockStatusItem()]) + expect(screen.getAllByTestId('status-item')).toHaveLength(1) }) test('should render with more than 4 status items correctly', () => { - const wrapper = getWrapper(mockManyStatusItems(5)) - expect(wrapper.find(StatusItems).prop('statusItems')).toHaveLength(5) + getScreen(mockManyStatusItems(5)) + expect(screen.getAllByTestId('status-item')).toHaveLength(4) + // Click the show all statuses button + fireEvent.click(screen.getByRole('button')) + expect(screen.getAllByTestId('status-item')).toHaveLength(5) }) describe('show/hide full status list', () => { describe('when over four items are present', () => { const numStatusItems = 10 - let wrapper - let linkWrapper + let showAllStatusesButton beforeEach(() => { - wrapper = getWrapper(mockManyStatusItems(numStatusItems)) - linkWrapper = wrapper.find('button').first() + getScreen(mockManyStatusItems(numStatusItems)) + showAllStatusesButton = screen.getByRole('button') }) test('should limit the statuses to 4 by default', () => { - expect(wrapper.find(StatusItems).prop('statusItems')).toHaveLength(numStatusItems) - expect(wrapper.find(StatusItems).prop('limit')).toEqual(4) + expect(screen.getAllByTestId('status-item')).toHaveLength(4) }) test('should have no limit after show all statuses clicked', () => { - linkWrapper.simulate('click') - expect(wrapper.find(StatusItems).prop('statusItems')).toHaveLength(numStatusItems) - expect(wrapper.find(StatusItems).prop('limit')).toEqual(numStatusItems) + fireEvent.click(showAllStatusesButton) + expect(screen.getAllByTestId('status-item')).toHaveLength(numStatusItems) }) test('should have correct link text before click', () => { - expect(linkWrapper.text()).toEqual('Show all status updates') + expect(screen.getByText('Show all status updates')).toBeInTheDocument() }) test('should have correct link text after click', () => { - linkWrapper.simulate('click') - expect(linkWrapper.text()).toEqual('Show all status updates') + fireEvent.click(showAllStatusesButton) + expect(screen.getByText('Show only recent status updates')).toBeInTheDocument() }) }) describe('when four or under items are present', () => { test('should not show the show/hide statuses link with exactly 4 items', () => { - const wrapper = getWrapper(mockManyStatusItems(4)) - expect(wrapper.find('button')).toHaveLength(0) + getScreen(mockManyStatusItems(4)) + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) test('should not show the show/hide statuses link with 1 item', () => { - const wrapper = getWrapper(mockManyStatusItems(1)) - expect(wrapper.find('button')).toHaveLength(0) + getScreen(mockManyStatusItems(1)) + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) }) }) diff --git a/spec/javascript/components/molecules/lease_up_sidebar/StatusItem.test.js b/spec/javascript/components/molecules/lease_up_sidebar/StatusItem.test.js index 4a1c156fd..9f38a69f9 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/StatusItem.test.js +++ b/spec/javascript/components/molecules/lease_up_sidebar/StatusItem.test.js @@ -1,64 +1,61 @@ import React from 'react' -import { mount, shallow } from 'enzyme' +import { render, screen } from '@testing-library/react' import StatusItem from 'components/molecules/lease_up_sidebar/StatusItem' import { mockStatusItem } from '../../../mocks/statusItemMock' -const getWrapper = (item) => shallow() +const getScreen = (item) => render() describe('StatusItem', () => { test('should render an approved status with substatus and comments correctly', () => { - const wrapper = getWrapper(mockStatusItem()) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem()) + expect(asFragment()).toMatchSnapshot() }) test('should render properly when no substatus provided', () => { - const wrapper = getWrapper(mockStatusItem({ substatus: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ substatus: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly when no comment provided', () => { - const wrapper = getWrapper(mockStatusItem({ comment: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ comment: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly when no comment or substatus provided', () => { - const wrapper = getWrapper(mockStatusItem({ substatus: null, comment: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ substatus: null, comment: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly with withdrawn status', () => { - const wrapper = getWrapper(mockStatusItem({ status: 'Withdrawn', substatus: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ status: 'Withdrawn', substatus: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly with Processing status', () => { - const wrapper = getWrapper(mockStatusItem({ status: 'Processing', substatus: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ status: 'Processing', substatus: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly with Appealed status', () => { - const wrapper = getWrapper(mockStatusItem({ status: 'Appealed', substatus: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ status: 'Appealed', substatus: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly with Disqualified status', () => { - const wrapper = getWrapper(mockStatusItem({ status: 'Disqualified', substatus: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ status: 'Disqualified', substatus: null })) + expect(asFragment()).toMatchSnapshot() }) test('should render properly with Lease Signed status', () => { - const wrapper = getWrapper(mockStatusItem({ status: 'Lease Signed', substatus: null })) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItem({ status: 'Lease Signed', substatus: null })) + expect(asFragment()).toMatchSnapshot() }) test('should format timestamps as expected', () => { - const wrapper = mount( - - ) - const timestamp = wrapper.find('StatusDate').text() - expect(timestamp).toEqual('Aug 25, 2020') + render() + expect(screen.getByText('Aug 25, 2020')).toBeInTheDocument() }) }) diff --git a/spec/javascript/components/molecules/lease_up_sidebar/StatusItems.test.js b/spec/javascript/components/molecules/lease_up_sidebar/StatusItems.test.js index 8efcf6bef..0545067af 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/StatusItems.test.js +++ b/spec/javascript/components/molecules/lease_up_sidebar/StatusItems.test.js @@ -1,14 +1,14 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render, within, screen } from '@testing-library/react' +import moment from 'moment' -import StatusItem from 'components/molecules/lease_up_sidebar/StatusItem' import StatusItems from 'components/molecules/lease_up_sidebar/StatusItems' import { mockManyStatusItems, mockStatusItem, mockStatusItems } from '../../../mocks/statusItemMock' -const getWrapper = (items, limit = undefined, height = null) => - shallow() +const getScreen = (items, limit = undefined, height = null) => + render() const TIMESTAMP_AUGUST_25_2020 = 1598338800 const TIMESTAMP_AUGUST_26_2020 = 1598400000 @@ -16,65 +16,67 @@ const TIMESTAMP_AUGUST_26_2020 = 1598400000 describe('StatusItems', () => { describe('snapshot tests', () => { test('should render empty status items correctly', () => { - const wrapper = getWrapper([]) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen([]) + expect(asFragment()).toMatchSnapshot() }) test('should render a single status item correctly', () => { - const wrapper = getWrapper([mockStatusItem()]) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen([mockStatusItem()]) + expect(asFragment()).toMatchSnapshot() }) test('should render multiple status items correctly', () => { - const wrapper = getWrapper(mockStatusItems()) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItems()) + expect(asFragment()).toMatchSnapshot() }) test('should render multiple status items with limit 0 correctly', () => { - const wrapper = getWrapper(mockStatusItems(), 0) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItems(), 0) + expect(asFragment()).toMatchSnapshot() }) test('should render multiple status items with limit 1 correctly', () => { - const wrapper = getWrapper(mockStatusItems(), 1) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItems(), 1) + expect(asFragment()).toMatchSnapshot() }) test('should render multiple status items with limit 100 correctly', () => { - const wrapper = getWrapper(mockStatusItems(), 100) - expect(wrapper).toMatchSnapshot() + const { asFragment } = getScreen(mockStatusItems(), 100) + expect(asFragment()).toMatchSnapshot() }) }) it('should not set a height if height is not provided', () => { - const wrapper = getWrapper(mockStatusItems(), 4) - const styles = wrapper.find('.status-items').props().style - expect(styles).toEqual({}) + const { asFragment } = getScreen(mockStatusItems(), 4) + expect(asFragment()).toMatchSnapshot() }) it('should limit the height of the list if provided', () => { - const wrapper = getWrapper(mockStatusItems(), 4, '20rem') - const styles = wrapper.find('.status-items').props().style - expect(styles).toEqual({ height: '20rem', overflow: 'scroll' }) + const { asFragment } = getScreen(mockStatusItems(), 4, '20rem') + expect(asFragment()).toMatchSnapshot() }) describe('with an empty status items list', () => { const statusItems = [] test('should render no status items when no limit is specified', () => { - expect(getWrapper(statusItems).find(StatusItem)).toHaveLength(0) + getScreen(statusItems) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) test('should render no status items when limit is 0', () => { - expect(getWrapper(statusItems, 0).find(StatusItem)).toHaveLength(0) + getScreen(statusItems) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) test('should render no status items when limit is 1', () => { - expect(getWrapper(statusItems, 1).find(StatusItem)).toHaveLength(0) + getScreen(statusItems, 1) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) test('should render no status items when limit is 4', () => { - expect(getWrapper(statusItems, 4).find(StatusItem)).toHaveLength(0) + getScreen(statusItems, 4) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) }) @@ -82,19 +84,23 @@ describe('StatusItems', () => { const statusItems = [mockStatusItem()] test('should render one status item when no limit is specified', () => { - expect(getWrapper(statusItems).find(StatusItem)).toHaveLength(1) + getScreen(statusItems) + expect(screen.getByTestId('status-item')).toBeInTheDocument() }) test('should render no status items when limit is 0', () => { - expect(getWrapper(statusItems, 0).find(StatusItem)).toHaveLength(0) + getScreen(statusItems, 0) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) test('should render one status item when limit is 1', () => { - expect(getWrapper(statusItems, 1).find(StatusItem)).toHaveLength(1) + getScreen(statusItems, 1) + expect(screen.getByTestId('status-item')).toBeInTheDocument() }) test('should render one status item when limit is 4', () => { - expect(getWrapper(statusItems, 4).find(StatusItem)).toHaveLength(1) + getScreen(statusItems, 4) + expect(screen.getByTestId('status-item')).toBeInTheDocument() }) }) @@ -102,49 +108,70 @@ describe('StatusItems', () => { const statusItems = mockManyStatusItems(5) test('should render five status items when no limit is specified', () => { - expect(getWrapper(statusItems).find(StatusItem)).toHaveLength(5) + getScreen(statusItems) + expect(screen.getAllByTestId('status-item')).toHaveLength(5) }) test('should render no status items when limit is 0', () => { - expect(getWrapper(statusItems, 0).find(StatusItem)).toHaveLength(0) + getScreen(statusItems, 0) + expect(screen.queryByTestId('status-item')).not.toBeInTheDocument() }) test('should render one status items when limit is 1', () => { - expect(getWrapper(statusItems, 1).find(StatusItem)).toHaveLength(1) + getScreen(statusItems, 1) + expect(screen.getByTestId('status-item')).toBeInTheDocument() }) test('should render four status items when limit is 4', () => { - expect(getWrapper(statusItems, 4).find(StatusItem)).toHaveLength(4) + getScreen(statusItems, 4) + expect(screen.getAllByTestId('status-item')).toHaveLength(4) }) }) describe('with two status items in chronological order', () => { - const statusItems = [ + const mockStatusItems = [ mockStatusItem({ timestamp: TIMESTAMP_AUGUST_25_2020 }), mockStatusItem({ timestamp: TIMESTAMP_AUGUST_26_2020 }) ] - test('should render status items in the order specified', () => { - const statusItemsWrapper = getWrapper(statusItems).find(StatusItem) - const firstStatusItem = statusItemsWrapper.first() - const secondStatusItem = statusItemsWrapper.at(1) - expect(firstStatusItem.prop('statusItem').timestamp).toEqual(TIMESTAMP_AUGUST_25_2020) - expect(secondStatusItem.prop('statusItem').timestamp).toEqual(TIMESTAMP_AUGUST_26_2020) + getScreen(mockStatusItems) + const statusItems = screen.getAllByTestId('status-item') + const firstStatusItem = statusItems[0] + const secondStatusItem = statusItems[1] + expect( + within(firstStatusItem).getByText( + moment.unix(TIMESTAMP_AUGUST_25_2020).format('MMM D, YYYY') + ) + ).toBeInTheDocument() + expect( + within(secondStatusItem).getByText( + moment.unix(TIMESTAMP_AUGUST_26_2020).format('MMM D, YYYY') + ) + ).toBeInTheDocument() }) }) describe('with two status items not in chronological order', () => { - const statusItems = [ + const mockStatusItems = [ mockStatusItem({ timestamp: TIMESTAMP_AUGUST_26_2020 }), mockStatusItem({ timestamp: TIMESTAMP_AUGUST_25_2020 }) ] test('should render status items in the order specified', () => { - const statusItemsWrapper = getWrapper(statusItems).find(StatusItem) - const firstStatusItem = statusItemsWrapper.first() - const secondStatusItem = statusItemsWrapper.at(1) - expect(firstStatusItem.prop('statusItem').timestamp).toEqual(TIMESTAMP_AUGUST_26_2020) - expect(secondStatusItem.prop('statusItem').timestamp).toEqual(TIMESTAMP_AUGUST_25_2020) + getScreen(mockStatusItems) + const statusItems = screen.getAllByTestId('status-item') + const firstStatusItem = statusItems[0] + const secondStatusItem = statusItems[1] + expect( + within(firstStatusItem).getByText( + moment.unix(TIMESTAMP_AUGUST_26_2020).format('MMM D, YYYY') + ) + ).toBeInTheDocument() + expect( + within(secondStatusItem).getByText( + moment.unix(TIMESTAMP_AUGUST_25_2020).format('MMM D, YYYY') + ) + ).toBeInTheDocument() }) }) }) diff --git a/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/LeaseUpSidebar.test.js.snap b/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/LeaseUpSidebar.test.js.snap index afa26e54b..69cdedacf 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/LeaseUpSidebar.test.js.snap +++ b/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/LeaseUpSidebar.test.js.snap @@ -1,82 +1,320 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`LeaseUpSidebar snapshot tests should match snapshot with 5 status items 1`] = ` -
    + - -
    - + +
    + + +
    +
    +
    +

    + Status History +

    +
    +
    +

    + Status History +

    +
    +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + + +
    + +
    -
    + `; diff --git a/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItem.test.js.snap b/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItem.test.js.snap index e3e8bc617..408e78116 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItem.test.js.snap +++ b/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItem.test.js.snap @@ -1,250 +1,331 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`StatusItem should render an approved status with substatus and comments correctly 1`] = ` -
    +
    - -
    -
    - Approval letter sent -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    -
    - - +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    +
    `; exports[`StatusItem should render properly when no comment or substatus provided 1`] = ` -
    +
    - +
    + Approved +
    +
    +
    + -
    -
    - - -
    -
    + `; exports[`StatusItem should render properly when no comment provided 1`] = ` -
    -
    - -
    +
    - Approval letter sent -
    -
    -
    - - +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + -
    + `; exports[`StatusItem should render properly when no substatus provided 1`] = ` -
    -
    - -
    +
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    -
    - - +
    +
    + Approved +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    +
    `; exports[`StatusItem should render properly with Appealed status 1`] = ` -
    -
    - -
    +
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    +
    + Appealed +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    - - -
    -
    +
    `; exports[`StatusItem should render properly with Disqualified status 1`] = ` -
    -
    - -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    +
    - - +
    +
    + Disqualified +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    +
    `; exports[`StatusItem should render properly with Lease Signed status 1`] = ` -
    -
    - -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    +
    - - +
    +
    + Lease Signed +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    +
    `; exports[`StatusItem should render properly with Processing status 1`] = ` -
    -
    - -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    +
    - - +
    +
    + Processing +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    +
    `; exports[`StatusItem should render properly with withdrawn status 1`] = ` -
    -
    - -
    +
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    -
    - - +
    +
    + Withdrawn +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + -
    +
    `; diff --git a/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItems.test.js.snap b/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItems.test.js.snap index 4890adfcd..65c5ecbef 100644 --- a/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItems.test.js.snap +++ b/spec/javascript/components/molecules/lease_up_sidebar/__snapshots__/StatusItems.test.js.snap @@ -1,201 +1,832 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`StatusItems should limit the height of the list if provided 1`] = ` + +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Unit selected +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Waiting for subsidy inspection +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Waitlisted +
    +
    +
    + Written confirmation sent +
    +
    + +
    + +`; + +exports[`StatusItems should not set a height if height is not provided 1`] = ` + +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Unit selected +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Waiting for subsidy inspection +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Waitlisted +
    +
    +
    + Written confirmation sent +
    +
    + +
    + +`; + exports[`StatusItems snapshot tests should render a single status item correctly 1`] = ` -
    - -
    + +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    + `; exports[`StatusItems snapshot tests should render empty status items correctly 1`] = ` -
    + +
    + `; exports[`StatusItems snapshot tests should render multiple status items correctly 1`] = ` -
    - - - - - - -
    + +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Unit selected +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Waiting for subsidy inspection +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Waitlisted +
    +
    +
    + Written confirmation sent +
    +
    + +
    +
    +
    + Lease Signed +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Lease Signed +
    +
    +
    + +
    + `; exports[`StatusItems snapshot tests should render multiple status items with limit 0 correctly 1`] = ` -
    + +
    + `; exports[`StatusItems snapshot tests should render multiple status items with limit 1 correctly 1`] = ` -
    - -
    + +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    + `; exports[`StatusItems snapshot tests should render multiple status items with limit 100 correctly 1`] = ` -
    - - - - - - -
    + +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Unit selected +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Waiting for subsidy inspection +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Waitlisted +
    +
    +
    + Written confirmation sent +
    +
    + +
    +
    +
    + Lease Signed +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Lease Signed +
    +
    +
    + +
    + `; diff --git a/spec/javascript/components/organisms/ConfirmationModal.test.js b/spec/javascript/components/organisms/ConfirmationModal.test.js index 9ec9fc948..f1ce1becc 100644 --- a/spec/javascript/components/organisms/ConfirmationModal.test.js +++ b/spec/javascript/components/organisms/ConfirmationModal.test.js @@ -1,11 +1,8 @@ import React from 'react' -import { shallow } from 'enzyme' +import { fireEvent, render, screen } from '@testing-library/react' import ConfirmationModal from 'components/organisms/ConfirmationModal' -import Modal from 'components/organisms/Modal' - -import { findWithText } from '../../testUtils/wrapperUtil' const PRIMARY_TEXT = 'Primary Text' const SECONDARY_TEXT = 'Secondary Text' @@ -16,8 +13,8 @@ const ON_CLOSE = jest.fn() const ON_PRIMARY_CLICK = jest.fn() const ON_SECONDARY_CLICK = jest.fn() -const getWrapper = (propOverrides = {}) => - shallow( +const getScreen = (propOverrides = {}) => + render( ) describe('ConfirmationModal', () => { + test('should render the modal as closed', () => { + const { asFragment } = getScreen() + expect(asFragment()).toMatchSnapshot() + }) + describe('with default props', () => { - let wrapper beforeEach(() => { - wrapper = getWrapper() - }) - - test('should render the modal as closed', () => { - expect(wrapper.find(Modal).prop('isOpen')).toBeFalsy() - expect(wrapper.find(Modal.Body).prop('hidden')).toBeFalsy() + getScreen({ isOpen: true }) }) test('should render the title and subtitle', () => { - expect(wrapper.find(Modal.Header).prop('title')).toEqual(TITLE) - expect(wrapper.find(Modal.Content).dive().text()).toEqual(SUBTITLE) + expect(screen.getByText(TITLE)).toBeInTheDocument() + expect(screen.getByText(SUBTITLE)).toBeInTheDocument() }) test('should render with the default header id', () => { - expect(wrapper.find(Modal.Header).prop('id')).toEqual('confirmation-modal-header') + expect(screen.getByText(TITLE).parentNode).toHaveAttribute('id', 'confirmation-modal-header') }) test('should render the primary button as a -
    -
    - - -
    -
    -
    -
    - } - > - -
    -
    - -
    -
    -
    - - - - - -`; +exports[`LeaveConfirmationModal it should render successfully 1`] = ``; diff --git a/spec/javascript/components/organisms/__snapshots__/PageHeader.test.js.snap b/spec/javascript/components/organisms/__snapshots__/PageHeader.test.js.snap new file mode 100644 index 000000000..5389ae1bc --- /dev/null +++ b/spec/javascript/components/organisms/__snapshots__/PageHeader.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PageHeader when content is empty does not render a content section 1`] = ` + +
    +
    +
    + +
    +

    + title1 +

    +
    + + + Export + + +
    +
    +
    +
    +
    +
    +`; diff --git a/spec/javascript/components/organisms/__snapshots__/SimpleModal.test.js.snap b/spec/javascript/components/organisms/__snapshots__/SimpleModal.test.js.snap index 1bd0ccb11..33eb386d7 100644 --- a/spec/javascript/components/organisms/__snapshots__/SimpleModal.test.js.snap +++ b/spec/javascript/components/organisms/__snapshots__/SimpleModal.test.js.snap @@ -1,961 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SimpleModal it should render status type successfully 1`] = ` - - - - -
    - -
    -
    - } - > - -
    -
    - - -
    - -
    -

    - Update Status -

    -
    -
    - - -
    - -
    - - - - - -

    - This change will affect this application's preferences -

    - -
    -
    - -
    -

    - This application would no longer be eligible for Live Work Preference -

    -

    - Note, you will have the opportunity to grant another household member this preference -

    -
    -
    -
    -
    -
    - -
    -
    - content -
    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - - -`; +exports[`SimpleModal it should render alert type successfully 1`] = ``; -exports[`SimpleModal it should render status type successfully 2`] = ` - - - - -
    - -
    -
    - } - > - -
    -
    - - -
    - -
    -

    - Update Status -

    -
    -
    - - -
    - -
    - - - - - -

    - This change will affect this application's preferences -

    - -
    -
    - -
    -

    - This application would no longer be eligible for Live Work Preference -

    -

    - Note, you will have the opportunity to grant another household member this preference -

    -
    -
    -
    -
    -
    - -
    -
    - content -
    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - - -`; +exports[`SimpleModal it should render default status type successfully 1`] = ``; diff --git a/spec/javascript/components/organisms/__snapshots__/StatusHistoryPopover.test.js.snap b/spec/javascript/components/organisms/__snapshots__/StatusHistoryPopover.test.js.snap index d0dcb8adc..790cb1df7 100644 --- a/spec/javascript/components/organisms/__snapshots__/StatusHistoryPopover.test.js.snap +++ b/spec/javascript/components/organisms/__snapshots__/StatusHistoryPopover.test.js.snap @@ -1,218 +1,410 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StatusHistoryPopover when there are fewer than 4 status items it matches snapshot when open 1`] = ` - - + +
    +
    +
    +
    - +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    - - - - +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + +`; + +exports[`StatusHistoryPopover when there are fewer than 4 status items it matches snapshot when open 1`] = ` + + +
    +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + +`; + +exports[`StatusHistoryPopover when there are more than 4 status items sets a fixed height for the status items component 1`] = ` + + +
    +
    +
    +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + +
    - -
    - -
    -
    - -
    - Approved -
    -
    -
    -
    - Approval letter sent -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    -
    - -
    - - -
    - Aug 25, 2020 -
    -
    -
    -
    - - -
    -
    - -
    - Approved -
    -
    -
    -
    - Approval letter sent -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. -
    -
    - -
    - - -
    - Aug 25, 2020 -
    -
    -
    -
    - -
    - +
    +
    + Approved +
    +
    +
    + Approval letter sent +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum condimentum lorem consectetur eleifend. +
    + - +
    +
    - - +
    + `; diff --git a/spec/javascript/components/organisms/__snapshots__/TabsSection.test.js.snap b/spec/javascript/components/organisms/__snapshots__/TabsSection.test.js.snap index 7bcb48f55..d13a9b421 100644 --- a/spec/javascript/components/organisms/__snapshots__/TabsSection.test.js.snap +++ b/spec/javascript/components/organisms/__snapshots__/TabsSection.test.js.snap @@ -1,113 +1,46 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TabsMenu it should render correctly 1`] = ` - +
    - - - + Supplemental Information + + +
    - -
    - - +
    + `; diff --git a/spec/javascript/components/supplemental_application/SupplementalApplicationPage.test.js b/spec/javascript/components/supplemental_application/SupplementalApplicationPage.test.js index 371a312af..e3e91b334 100644 --- a/spec/javascript/components/supplemental_application/SupplementalApplicationPage.test.js +++ b/spec/javascript/components/supplemental_application/SupplementalApplicationPage.test.js @@ -1,9 +1,9 @@ +import { render, screen, fireEvent, within, act } from '@testing-library/react' import { cloneDeep, merge } from 'lodash' -import { act } from 'react-dom/test-utils' -import renderer from 'react-test-renderer' +import selectEvent from 'react-select-event' import supplementalApplication from '../../fixtures/supplemental_application' -import { leaseUpAppWithUrl, mountAppWithUrl } from '../../testUtils/wrapperUtil' +import { leaseUpAppWithUrl, renderAppWithUrl } from '../../testUtils/wrapperUtil' const mockInitialLoad = jest.fn() const mockSubmitApplication = jest.fn() @@ -20,7 +20,6 @@ const getWindowUrl = (id) => `/lease-ups/applications/${id}` const ID_NO_AVAILABLE_UNITS = 'idwithnoavailableunits' const ID_WITH_TOTAL_MONTHLY_RENT = 'idwithtotalmonthlyrent' -const ID_WITH_SELECTED_UNIT = 'idwithselectedunit' /** * TODO: instead of mocking apiService, we should probably be mocking one level up (actions.js). @@ -164,16 +163,8 @@ jest.mock('apiService', () => { updateRentalAssistance: async (_) => ({}) } }) - -const getWrapper = async (id = getMockApplication().id, unitId = null) => { - let wrapper - await act(async () => { - wrapper = mountAppWithUrl(getWindowUrl(id)) - }) - - wrapper.update() - - return wrapper +const getWrapper = async (id = getMockApplication().id) => { + return await act(async () => renderAppWithUrl(getWindowUrl(id))) } describe('SupplementalApplicationPage', () => { @@ -194,20 +185,18 @@ describe('SupplementalApplicationPage', () => { }) test('it should render as expected', async () => { - let component + const { asFragment } = await act(() => + render(leaseUpAppWithUrl(getWindowUrl(getMockApplication().id))) + ) - await renderer.act(async () => { - component = renderer.create(leaseUpAppWithUrl(getWindowUrl(getMockApplication().id))) - }) - - const tree = component.toJSON() - expect(tree).toMatchSnapshot() + expect(asFragment()).toMatchSnapshot() }) test('it only performs initial load request if nothing is changed', async () => { - const wrapper = await getWrapper() - await act(async () => { - wrapper.find('form').first().simulate('submit') + await getWrapper() + + await act(() => { + fireEvent.submit(screen.getByRole('form')) }) expect(mockInitialLoad.mock.calls).toHaveLength(1) @@ -216,11 +205,21 @@ describe('SupplementalApplicationPage', () => { test('it only updates changed fields', async () => { const payload = getMockApplication() - const wrapper = await getWrapper() - wrapper.find('#demographics-dependents select option[value=2]').simulate('change') - await act(async () => { - wrapper.find('form').first().simulate('submit') + await getWrapper() + + await act(() => + fireEvent.change( + screen.getByRole('combobox', { + name: /number of dependents/i + }), + { target: { value: '2' } } + ) + ) + + await act(() => { + fireEvent.submit(screen.getByRole('form')) }) + expect(mockSubmitApplication.mock.calls).toHaveLength(1) expect(mockSubmitApplication).toHaveBeenCalledWith({ id: payload.id, @@ -239,17 +238,42 @@ describe('SupplementalApplicationPage', () => { applicant: { marital_status: 'Domestic Partner' } } - const wrapper = await getWrapper() - - wrapper.find('#demographics-dependents select option[value=2]').simulate('change') - wrapper.find('#demographics-seniors select option[value=3]').simulate('change') - wrapper.find('#demographics-minors select option[value=0]').simulate('change') - wrapper - .find('#demographics-marital-status select option[value="Domestic Partner"]') - .simulate('change') + await getWrapper() - await act(async () => { - wrapper.find('form').first().simulate('submit') + await act(() => + fireEvent.change( + screen.getByRole('combobox', { + name: /number of dependents/i + }), + { target: { value: '2' } } + ) + ) + + await act(() => + fireEvent.change( + screen.getByRole('combobox', { + name: /number of seniors/i + }), + { target: { value: '3' } } + ) + ) + await act(() => + fireEvent.change( + screen.getByRole('combobox', { + name: /number of minors/i + }), + { target: { value: '0' } } + ) + ) + fireEvent.change( + screen.getByRole('combobox', { + name: /primary applicant marital status/i + }), + { target: { value: 'Domestic Partner' } } + ) + + await act(() => { + fireEvent.submit(screen.getByRole('form')) }) expect(mockSubmitApplication.mock.calls).toHaveLength(1) @@ -258,15 +282,32 @@ describe('SupplementalApplicationPage', () => { describe('preference panel', () => { test('it saves a live/work application preference panel', async () => { - const wrapper = await getWrapper() + await getWrapper() + expect(screen.getAllByTestId('expandable-table-row')[2]).toHaveAttribute( + 'aria-expanded', + 'false' + ) // Click edit to open up the panel act(() => { - wrapper.find('.preferences-table .action-link').at(2).simulate('click') + fireEvent.click( + within(screen.getAllByTestId('expandable-table-row')[2]).getByRole('button', { + name: /edit/i + }) + ) }) + + expect(screen.getAllByTestId('expandable-table-row')[2]).toHaveAttribute( + 'aria-expanded', + 'true' + ) // Save the preference panel without making updates await act(async () => { - wrapper.find('.preferences-table .save-panel-btn').at(2).simulate('click') + fireEvent.click( + within(screen.getAllByTestId('expandable-table-row-button')[2]).getByRole('button', { + name: /save/i + }) + ) }) const expectedPreferencePayload = { @@ -283,13 +324,23 @@ describe('SupplementalApplicationPage', () => { }) test('it updates total monthly rent when saving a rent burdened preference panel', async () => { - const wrapper = await getWrapper(ID_WITH_TOTAL_MONTHLY_RENT) + await getWrapper(ID_WITH_TOTAL_MONTHLY_RENT) // Click edit to open up the panel - wrapper.find('.preferences-table .action-link').first().simulate('click') + act(() => { + fireEvent.click( + within(screen.getAllByTestId('expandable-table-row')[0]).getByRole('button', { + name: /edit/i + }) + ) + }) // Save the preference panel without making updates await act(async () => { - wrapper.find('.preferences-table .save-panel-btn').first().simulate('click') + fireEvent.click( + within(screen.getAllByTestId('expandable-table-row-button')[0]).getByRole('button', { + name: /save/i + }) + ) }) const expectedPreferencePayload = { @@ -321,20 +372,19 @@ describe('SupplementalApplicationPage', () => { describe('confirmed income section', () => { test('currencies are converted to floats on save', async () => { const application = getMockApplication() - const wrapper = await getWrapper() + await getWrapper() act(() => { - wrapper - .find('input#form-confirmed_household_annual_income') - .simulate('change', { target: { value: '1234' } }) - }) - - act(() => { - wrapper.find('input#form-confirmed_household_annual_income').simulate('focus') + fireEvent.change( + screen.getByRole('textbox', { + name: /confirmed total household annual income/i + }), + { target: { value: '1234' } } + ) }) await act(async () => { - wrapper.find('form').first().simulate('submit') + fireEvent.submit(screen.getByRole('form')) }) const expectedApplication = { @@ -343,7 +393,7 @@ describe('SupplementalApplicationPage', () => { has_military_service: 'No', reserved_senior: 'No' } - expectedApplication.confirmed_household_annual_income = 1234.0 + expectedApplication.confirmed_household_annual_income = '1234' expect(mockSubmitApplication.mock.calls).toHaveLength(1) expect(mockSubmitApplication).toHaveBeenCalledWith(expectedApplication) @@ -351,26 +401,27 @@ describe('SupplementalApplicationPage', () => { test('converts empty values to null when touched', async () => { const application = getMockApplication() - const wrapper = await getWrapper() + await getWrapper() act(() => { - wrapper - .find('input#form-confirmed_household_annual_income') - .simulate('change', { target: { value: '' } }) - - wrapper.find('input#form-confirmed_household_annual_income').simulate('focus') + fireEvent.change( + screen.getByRole('textbox', { + name: /confirmed total household annual income/i + }), + { target: { value: '' } } + ) }) + // We don't expect there to be a value for confirmed_household_annual_income const expectedApplication = { id: application.id, has_developmental_disability: 'No', has_military_service: 'No', reserved_senior: 'No' } - expectedApplication.confirmed_household_annual_income = null await act(async () => { - wrapper.find('form').first().simulate('submit') + fireEvent.submit(screen.getByRole('form')) }) expect(mockSubmitApplication.mock.calls).toHaveLength(1) expect(mockSubmitApplication).toHaveBeenCalledWith(expectedApplication) @@ -378,7 +429,7 @@ describe('SupplementalApplicationPage', () => { }) describe('Lease Section', () => { - let wrapper, mockApplication + let mockApplication beforeEach(async () => { mockApplication = getMockApplication() @@ -387,59 +438,93 @@ describe('SupplementalApplicationPage', () => { id: 'validDTHPPref', post_lottery_validation: 'Confirmed' }) - - wrapper = await getWrapper() }) test('should save a lease object', async () => { - wrapper.find('#edit-lease-button').first().simulate('click') + await getWrapper() + + fireEvent.click(screen.getByRole('button', { name: /edit lease/i })) + + const leaseStartDate = screen.getAllByTestId('multi-date-field')[1] + const monthInput = within(leaseStartDate).getAllByRole('textbox')[0] + const dayInput = within(leaseStartDate).getAllByRole('textbox')[1] + const yearInput = within(leaseStartDate).getAllByRole('textbox')[2] + expect(monthInput).toHaveAttribute('placeholder', 'MM') + expect(dayInput).toHaveAttribute('placeholder', 'DD') + expect(yearInput).toHaveAttribute('placeholder', 'YYYY') // Fill out lease fields // Assigned Unit number - // Lease start date - act(() => { - wrapper - .find('#form-lease_unit') - .find('Select') - .props() - .onChange({ value: 'unit_without_priority' }) + selectEvent.openMenu( + within( + screen.getByRole('button', { + name: /assigned unit number/i + }) + ).getByRole('combobox') + ) - wrapper.find('#lease_start_date_month input').simulate('change', { target: { value: '1' } }) - wrapper.find('#lease_start_date_day input').simulate('change', { target: { value: '12' } }) + // Lease start date + await act(() => { + fireEvent.click(screen.getByText(/unit without priority/i)) - wrapper - .find('#lease_start_date_year input') - .simulate('change', { target: { value: '2019' } }) + fireEvent.change(monthInput, { + target: { value: '1' } + }) + fireEvent.change(dayInput, { + target: { value: '12' } + }) + fireEvent.change(yearInput, { + target: { value: '2019' } + }) // Preference used - wrapper - .find('[name="lease.preference_used"] select option[value="validDTHPPref"]') - .simulate('change') + fireEvent.change( + screen.getByRole('combobox', { + name: /preference used/i + }), + { target: { value: 'validDTHPPref' } } + ) }) - wrapper.update() // Costs - // need to set this to "Yes" first to be able to access the other parking cost fields. - wrapper - .find('[name="lease.bmr_parking_space_assigned"] select') - .at(0) - .simulate('change', { target: { value: 'Yes' } }) - - wrapper - .find('[name="lease.total_monthly_rent_without_parking"] input') - .simulate('change', { target: { value: '$1' } }) - wrapper - .find('[name="lease.monthly_parking_rent"] input') - .at(0) - .simulate('change', { target: { value: '$2' } }) - wrapper - .find('[name="lease.monthly_tenant_contribution"] input') - .simulate('change', { target: { value: '$3' } }) + await act(() => { + fireEvent.change( + screen.getByRole('combobox', { + name: /bmr parking space assigned\?/i + }), + { target: { value: 'Yes' } } + ) + }) + + fireEvent.change( + screen.getByRole('textbox', { + name: /monthly rent/i + }), + { + target: { + value: '$1' + } + } + ) + + fireEvent.change( + screen.getByRole('textbox', { + name: /monthly cost/i + }), + { target: { value: '$2' } } + ) + + fireEvent.change( + screen.getByRole('textbox', { + name: /tenant contribution/i + }), + { target: { value: '$3' } } + ) // Assert that they're sent to the API await act(async () => { - wrapper.find('form').first().simulate('submit') + fireEvent.submit(screen.getByRole('form')) }) const expectedLease = { @@ -459,141 +544,146 @@ describe('SupplementalApplicationPage', () => { expect(mockCreateLease.mock.calls).toHaveLength(0) expect(mockUpdateLease.mock.calls).toHaveLength(1) expect(mockUpdateLease).toHaveBeenCalledWith(expectedLease) - }) - - test('should send a null value for unit to API if selected', async () => { - const wrapper = await getWrapper(ID_WITH_SELECTED_UNIT) - wrapper.find('#edit-lease-button').first().simulate('click') - - // Select the value from the dropdown - - act(() => { - wrapper.find('#form-lease_unit').find('Select').props().onChange({ value: null }) - }) - wrapper.update() - - // Hit save - await act(async () => { - wrapper.find('form').first().simulate('submit') - }) - - // Verify that the API was called with null unit value - expect(mockCreateLease.mock.calls).toHaveLength(0) - expect(mockUpdateLease.mock.calls).toHaveLength(1) - expect(mockUpdateLease).toHaveBeenCalledWith(expect.objectContaining({ unit: null })) - }) + }, 10000) test('it displays "No Units Available" and 0 units count when no units available', async () => { - const wrapper = await getWrapper(ID_NO_AVAILABLE_UNITS) - const unitSelect = wrapper.find('#form-lease_unit').first() + await getWrapper(ID_NO_AVAILABLE_UNITS) + const unitSelect = screen.getByRole('button', { + name: /assigned unit number/i + }) - expect(unitSelect.exists()).toBeTruthy() - expect(unitSelect.html().includes('No Units Available')).toBeTruthy() - expect(wrapper.find('#total-available-count').text()).toEqual('0') + expect(within(unitSelect).getByText('No Units Available')).toBeInTheDocument() + expect(screen.getByTestId('total-available-count').textContent).toEqual('0') }) describe('when no unit is selected', () => { + beforeEach(async () => { + await getWrapper() + }) + test('it shows expected available unit counts', () => { - expect(wrapper.find('#total-available-count').text()).toEqual('2') - expect(wrapper.find('#accessibility-available-count').text()).toEqual('1') - expect(wrapper.find('#dthp-available-count').text()).toEqual('3') - expect(wrapper.find('#nrhp-available-count').text()).toEqual('4') + expect(screen.getByTestId('total-available-count').textContent).toEqual('2') + expect(screen.getByTestId('accessibility-available-count').textContent).toEqual('1') + expect(screen.getByTestId('dthp-available-count').textContent).toEqual('3') + expect(screen.getByTestId('nrhp-available-count').textContent).toEqual('4') }) test('it does not change the DTHP availability count when pref used is selected', () => { act(() => { - wrapper - .find('[name="lease.preference_used"] select') - .simulate('change', { target: { value: 'validDTHPPref' } }) + fireEvent.change( + screen.getByRole('combobox', { + name: /preference used/i + }), + { target: { value: 'validDTHPPref' } } + ) }) - expect(wrapper.find('#dthp-available-count').text()).toEqual('3') + expect(screen.getByTestId('dthp-available-count').textContent).toEqual('3') }) }) describe('when unit without priority is selected', () => { beforeEach(async () => { - wrapper.find('#edit-lease-button').first().simulate('click') + await getWrapper() + + fireEvent.click(screen.getByRole('button', { name: /edit lease/i })) + + selectEvent.openMenu( + within( + screen.getByRole('button', { + name: /assigned unit number/i + }) + ).getByRole('combobox') + ) + act(() => { - wrapper - .find('#form-lease_unit') - .find('Select') - .props() - .onChange({ value: 'unit_without_priority' }) + fireEvent.click(screen.getByText(/unit without priority/i)) }) - wrapper.update() }) test('it decreases the number of available units', () => { - expect(wrapper.find('#total-available-count').text()).toEqual('1') + expect(screen.getByTestId('total-available-count').textContent).toEqual('1') }) test('it does not impact the number of accessibility units', () => { - expect(wrapper.find('#accessibility-available-count').text()).toEqual('1') + expect(screen.getByTestId('accessibility-available-count').textContent).toEqual('1') }) test('it does not impact the number of priority set asides', () => { - expect(wrapper.find('#dthp-available-count').text()).toEqual('3') - expect(wrapper.find('#nrhp-available-count').text()).toEqual('4') + expect(screen.getByTestId('dthp-available-count').textContent).toEqual('3') + expect(screen.getByTestId('nrhp-available-count').textContent).toEqual('4') }) test('it decreases the available DTHP count when DTHP is pref used', () => { act(() => { - wrapper - .find('[name="lease.preference_used"] select') - .simulate('change', { target: { value: 'validDTHPPref' } }) + fireEvent.change( + screen.getByRole('combobox', { + name: /preference used/i + }), + { target: { value: 'validDTHPPref' } } + ) }) - expect(wrapper.find('#dthp-available-count').text()).toEqual('2') + expect(screen.getByTestId('dthp-available-count').textContent).toEqual('2') }) }) describe('when unit with priority is selected', () => { beforeEach(async () => { - wrapper.find('#edit-lease-button').first().simulate('click') - act(() => { - wrapper - .find('#form-lease_unit') - .find('Select') - .props() - .onChange({ value: 'unit_with_priority' }) + await getWrapper() + + fireEvent.click(screen.getByRole('button', { name: /edit lease/i })) + + selectEvent.openMenu( + within( + screen.getByRole('button', { + name: /assigned unit number/i + }) + ).getByRole('combobox') + ) + + await act(async () => { + await fireEvent.click(screen.getByText(/unit with priority/i)) }) - wrapper.update() }) test('it decreases the number of available units', () => { - expect(wrapper.find('#total-available-count').text()).toEqual('1') + expect(screen.getByTestId('total-available-count').textContent).toEqual('1') }) test('it decreases the number of accessibility units', () => { - expect(wrapper.find('#accessibility-available-count').text()).toEqual('0') + expect(screen.getByTestId('accessibility-available-count').textContent).toEqual('0') }) }) }) describe('Status Sidebar', () => { test('should render the LeaseUpSidebar', async () => { - const wrapper = await getWrapper() + await getWrapper() // Check that the page matches the snapshot that we have stored // of how the dropdown button and dropdown menu should render // when the dropdown menu is open - expect(wrapper.find('.sidebar-content')).toHaveLength(1) + expect(document.querySelector('.sidebar-content')).toBeInTheDocument(1) }) }) test('should display alert box when form is invalid on submit', async () => { - const wrapper = await getWrapper() + await getWrapper() + + const leaseStartDate = screen.getAllByTestId('multi-date-field')[1] + const monthInput = within(leaseStartDate).getAllByRole('textbox')[0] // Fill in letters in lease date month - which are invalid values act(() => { - wrapper - .find('#lease_start_date_month') - .first() - .simulate('change', { target: { value: 'AB' } }) + fireEvent.change(monthInput, { + target: { value: 'AB' } + }) }) - wrapper.find('#save-supplemental-application').first().simulate('click') + fireEvent.click(screen.getAllByRole('button', { name: /save/i }).pop()) // alert box to display - expect(wrapper.find('.alert-box').exists()).toBeTruthy() + expect( + screen.getByText(/please resolve any errors before saving the application\./i) + ).toBeInTheDocument() }) }) diff --git a/spec/javascript/components/supplemental_application/__snapshots__/SupplementalApplicationPage.test.js.snap b/spec/javascript/components/supplemental_application/__snapshots__/SupplementalApplicationPage.test.js.snap index 7ac7842c4..acf61279f 100644 --- a/spec/javascript/components/supplemental_application/__snapshots__/SupplementalApplicationPage.test.js.snap +++ b/spec/javascript/components/supplemental_application/__snapshots__/SupplementalApplicationPage.test.js.snap @@ -1,46 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SupplementalApplicationPage it should render as expected 1`] = ` -[ +
    -
    , +
    -
    , +

    Preferences and Priorities

    -

    - Complete this section first. - + Complete this section first. You must confirm claimed preferences before sending out a post-lottery letter. - - Please allow the applicant 24 hours to provide appropriate preference proof if not previously supplied. + Please allow the applicant 24 hours to provide appropriate preference proof if not previously supplied.

    -

    +

    Their document must include:
    • Their name @@ -167,130 +148,114 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = ` The address where they live or work in San Francisco
    • - A date on or after - February 14th, 2020 - (45 days before they submitted their application) + A date on or after February 14th, 2020 (45 days before they submitted their application)
    -

    -

    +
    +

    Confirmed Preferences

    + /> + /> + class="" + />
    - - Preference Claimant Rank Proof Status - -
    Rent Burdened Assisted Housing - -
    Unconfirmed
    Rent Burdened Housing Preference
    Assisted Housing Preference Automated Test Lease Up Application (do not modify) 1 Unconfirmed
    Assisted Housing Preference

    Do not give this preference unless applicant has provided a lease. If the person who lives in assisted housing is not the primary applicant, their address must have been provided at the time of the original application.

    @@ -635,23 +583,19 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `
    Live in San Francisco Preference Automated Test Lease Up Application (do not modify) 1
    • @@ -704,15 +649,14 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `
    Confirmed
    Live or Work in San Francisco Preference

    Please check to make sure that a document proving the preference address is provided. If no proof document is provided, do not confirm this preference.

    @@ -994,23 +923,19 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Application Signature

    Complete this section by filling in the date of the applicant’s supplemental application once it has been received. Be sure to enter the date from the application itself, not the date on which you received it.

    Household Reserved and Priority Units

    Income

    -

    Complete this section after MOHCD has confirmed the household’s income eligibility. You must complete this section even if the household is over or under income eligibility. -

    +

    Confirmed Household Income

    @@ -1417,28 +1281,25 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Not including imputed income from assets @@ -1513,34 +1368,31 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `
    Includes imputed income from assets, if applicable @@ -1550,41 +1402,34 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Area Median Income

    @@ -1713,42 +1550,38 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Demographics

    -

    Complete this section when a household has gone through income qualification. You must complete this section even if the household is over or under income eligibility. -

    +
    Check the listing to verify the age cutoff for seniors. @@ -1900,29 +1729,25 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Lease

    -

    If the household receives recurring rental assistance, remember to subtract this from the unit’s rent when calculating Tenant Contribution. -

    +

    Unit and Parking

    Remaining Available Units and Set-Asides @@ -2080,21 +1897,22 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `
    Total

    2 @@ -2102,18 +1920,19 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Accessibility

    1 @@ -2121,18 +1940,19 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    DTHP

    3 @@ -2140,18 +1960,19 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    NRHP

    4 @@ -2160,16 +1981,16 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `

    Household Information @@ -2177,39 +1998,39 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `
    Members

    1

    Accessibility Requests

    None @@ -2217,62 +2038,71 @@ exports[`SupplementalApplicationPage it should render as expected 1`] = `