diff --git a/app/models/legacy_appeal.rb b/app/models/legacy_appeal.rb index be859405409..3cbb10d458b 100644 --- a/app/models/legacy_appeal.rb +++ b/app/models/legacy_appeal.rb @@ -399,6 +399,8 @@ def outcoded_by_name end def contested_claim + return false unless FeatureToggle.enabled?(:indicator_for_contested_claims) + vacols_representatives&.any? do |r| VACOLS::Representative::CONTESTED_REPTYPES.values.pluck(:code).include?(r.reptype) end diff --git a/client/app/components/ErrorBoundary.jsx b/client/app/components/ErrorBoundary.jsx index 34e72e4c02f..a3910614dd2 100644 --- a/client/app/components/ErrorBoundary.jsx +++ b/client/app/components/ErrorBoundary.jsx @@ -19,6 +19,7 @@ export class ErrorBoundary extends React.Component { error, info, }); + console.log("Error caught:", error, info); } render() { diff --git a/client/app/index.js b/client/app/index.js index f41147999b5..8be1304ee20 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -10,6 +10,7 @@ import 'pdfjs-dist/web/pdf_viewer.css'; import React, { Suspense } from 'react'; import ReactOnRails from 'react-on-rails'; import { render } from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { forOwn } from 'lodash'; import { BrowserRouter, Switch } from 'react-router-dom'; @@ -153,8 +154,11 @@ const componentWrapper = (component) => (props, railsContext, domNodeId) => { const renderApp = (Component) => { const element = wrapComponent(Component); + const container = document.getElementById(domNodeId); + const root = createRoot(container); - render(element, document.getElementById(domNodeId)); + root.render(element); + // render(element, document.getElementById(domNodeId)); }; renderApp(component); diff --git a/client/app/queue/QueueLoadingScreen.jsx b/client/app/queue/QueueLoadingScreen.jsx index 566563b0e11..2044cf949b4 100644 --- a/client/app/queue/QueueLoadingScreen.jsx +++ b/client/app/queue/QueueLoadingScreen.jsx @@ -45,7 +45,6 @@ class QueueLoadingScreen extends React.PureComponent { if (role === 'sct_coordinator' && userIsSCTCoordinator && type === 'assign') { return this.props.fetchSpecialtyCaseTeamTasks(chosenUserId, userRole, type); } - return this.props.fetchAmaTasksOfUser(chosenUserId, userRole, type); } @@ -102,12 +101,16 @@ class QueueLoadingScreen extends React.PureComponent { if (this.isUserId(userUrlParam)) { const targetUserId = parseInt(userUrlParam, 10); - return ApiUtil.get(`/user?id=${targetUserId}`).then((resp) => - this.props.setTargetUser(resp.body.user)); + return ApiUtil.get(`/user?id=${targetUserId}`).then((resp) => { + this.props.setTargetUser(resp.body.user); + return resp.body.user; // Ensure the user is returned + }); } - return ApiUtil.get(`/user?css_id=${userUrlParam}`).then((resp) => - this.props.setTargetUser(resp.body.user)); + return ApiUtil.get(`/user?css_id=${userUrlParam}`).then((resp) => { + this.props.setTargetUser(resp.body.user); + return resp.body.user; // Ensure the user is returned + }); } isUserId = (str) => { @@ -125,8 +128,8 @@ class QueueLoadingScreen extends React.PureComponent { } createLoadPromise = () => { - return this.maybeLoadTargetUserInfo().then(() => { - const chosenUserId = this.props.targetUserId || this.props.userId; + return this.maybeLoadTargetUserInfo().then((result) => { + const chosenUserId = result?.id || this.props.targetUserId || this.props.userId; const userIsCamoEmployee = this.props.userIsCamoEmployee; return Promise.all([ diff --git a/client/app/queue/UserManagement.jsx b/client/app/queue/UserManagement.jsx index 7bbbae669b1..6619e4b3ce3 100644 --- a/client/app/queue/UserManagement.jsx +++ b/client/app/queue/UserManagement.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { flushSync } from 'react-dom'; import { css } from 'glamor'; import { sprintf } from 'sprintf-js'; @@ -33,6 +34,7 @@ export default class UserManagement extends React.PureComponent { // Search functions asyncLoadUser = (inputValue) => { // don't search till we have min length input + if (inputValue.length < 2) { this.setState({ remainingUsers: [] }); @@ -42,7 +44,9 @@ export default class UserManagement extends React.PureComponent { return ApiUtil.get(`/users?css_id=${inputValue}`).then((response) => { const users = response.body.users.data; - this.setState({ remainingUsers: users }); + flushSync(() => { + this.setState({ remainingUsers: users }); + }); return this.dropdownOptions(); }); @@ -68,7 +72,7 @@ export default class UserManagement extends React.PureComponent { } }); }); - } + }; // Status functions diff --git a/client/app/queue/caseEvaluation/CaseTimelinessTimeline.jsx b/client/app/queue/caseEvaluation/CaseTimelinessTimeline.jsx index 0c002f0ef4b..5293e6701ff 100644 --- a/client/app/queue/caseEvaluation/CaseTimelinessTimeline.jsx +++ b/client/app/queue/caseEvaluation/CaseTimelinessTimeline.jsx @@ -5,6 +5,7 @@ import COPY from '../../../COPY'; import { AttorneyDaysWorked } from './AttorneyDaysWorked'; import { AttorneyTasksTreeTimeline } from './AttorneyTasksTreeTimeline'; import { LegacyCaseTimeline } from './LegacyCaseTimeline'; +import ErrorMessage from '../../certification/components/ErrorMessage'; export const CaseTimelinessTimeline = (props) => { const { appeal, @@ -14,6 +15,10 @@ export const CaseTimelinessTimeline = (props) => { displayCaseTimelinessTimeline, } = props; + if (!task) { + return ; + } + let dateAssigned = moment(task.previousTaskAssignedOn); const decisionSubmitted = moment(task.assignedOn); diff --git a/client/app/queue/caseEvaluation/EvaluateDecisionView.jsx b/client/app/queue/caseEvaluation/EvaluateDecisionView.jsx index 60d48ea53e5..c4e0ba085e6 100644 --- a/client/app/queue/caseEvaluation/EvaluateDecisionView.jsx +++ b/client/app/queue/caseEvaluation/EvaluateDecisionView.jsx @@ -320,14 +320,23 @@ const mapStateToProps = (state, ownProps) => { attorneyChildrenTasks = getLegacyTaskTree(state, { appealId: appeal.externalId, judgeDecisionReviewTask }); } else { - // Get all tasks under the JudgeDecisionReviewTask - // Filters out those without a closedAt date or that are hideFromCaseTimeline - attorneyChildrenTasks = getTaskTreesForAttorneyTasks(state, { - appealId: appeal.externalId, judgeDecisionReviewTaskId: judgeDecisionReviewTask.uniqueId - }); + if (judgeDecisionReviewTask && judgeDecisionReviewTask.uniqueId) { + // Get all tasks under the JudgeDecisionReviewTask + // Filters out those without a closedAt date or that are hideFromCaseTimeline + attorneyChildrenTasks = getTaskTreesForAttorneyTasks(state, { + appealId: appeal.externalId, judgeDecisionReviewTaskId: judgeDecisionReviewTask.uniqueId + }); + } else { + console.error('Judge Decision Review Task or uniqueId is undefined'); + attorneyChildrenTasks = []; + } } } + console.log('State:', state); + console.log('Task ID:', ownProps.taskId); + console.log('Judge Decision Review Task:', judgeDecisionReviewTask); + return { appeal, attorneyChildrenTasks, diff --git a/client/app/queue/colocatedTasks/AddColocatedTaskView.jsx b/client/app/queue/colocatedTasks/AddColocatedTaskView.jsx index a335329b206..4ebe457187e 100644 --- a/client/app/queue/colocatedTasks/AddColocatedTaskView.jsx +++ b/client/app/queue/colocatedTasks/AddColocatedTaskView.jsx @@ -119,9 +119,15 @@ class AddColocatedTaskView extends React.PureComponent { this.props.setAppealAttrs(task.externalAppealId, { location: 'CASEFLOW' }); this.props.deleteTask(task.uniqueId); } + + const nextStepUrl = this.getNextStepUrl(); + if (nextStepUrl) { + this.props.history.push(nextStepUrl); + } }). - catch(() => { - // handle the error from the frontend + catch((error) => { + console.error('Error in goToNextStep:', error); + // Handle the error appropriately }); }; diff --git a/client/app/queue/components/AssignToAttorneyWidget.jsx b/client/app/queue/components/AssignToAttorneyWidget.jsx index 1360896f60e..7fccd7d5808 100644 --- a/client/app/queue/components/AssignToAttorneyWidget.jsx +++ b/client/app/queue/components/AssignToAttorneyWidget.jsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import { flushSync } from 'react-dom'; import { useSelector, useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { css } from 'glamor'; @@ -393,7 +394,7 @@ const AssignToAttorneyWidgetContainer = (props) => { showSuccessMessage={(val) => dispatch(showSuccessMessage(val))} resetSuccessMessages={(val) => dispatch(resetSuccessMessages(val))} resetAssignees={() => dispatch(resetAssignees())} - saveFailure={() => dispatch({ type: ACTIONS.SAVE_FAILURE })} + saveFailure={() => flushSync(() => dispatch({ type: ACTIONS.SAVE_FAILURE }))} {...props} /> ); diff --git a/client/app/queue/mtv/checkout/MotionToVacateFlowContainer.jsx b/client/app/queue/mtv/checkout/MotionToVacateFlowContainer.jsx index 983dc150c1b..ec3ad372c32 100644 --- a/client/app/queue/mtv/checkout/MotionToVacateFlowContainer.jsx +++ b/client/app/queue/mtv/checkout/MotionToVacateFlowContainer.jsx @@ -21,11 +21,12 @@ export const MotionToVacateFlowContainer = () => { const appeal = useSelector((state) => appealWithDetailSelector(state, { appealId })); - const steps = useMemo(() => getSteps(appeal), [appeal.type, appeal.vacateType]); + // const steps = useMemo(() => getSteps(appeal), [appeal.type, appeal.vacateType]); + const steps = useMemo(() => getSteps(appeal), [appeal?.type, appeal?.vacateType]); const initialState = { // cloning the individual issues - decisionIssues: appeal.decisionIssues.map((issue) => ({ ...issue })), + decisionIssues: appeal?.decisionIssues.map((issue) => ({ ...issue })), steps, getNextUrl: (current) => (getNextStep(current, steps) ? `${basePath}/${getNextStep(current, steps)}` : null), getPrevUrl: (current) => (getPrevStep(current, steps) ? `${basePath}/${getPrevStep(current, steps)}` : null) diff --git a/client/app/queue/mtv/checkout/mtvCheckoutSteps.js b/client/app/queue/mtv/checkout/mtvCheckoutSteps.js index 7ee784d09f5..3d9cf615da1 100644 --- a/client/app/queue/mtv/checkout/mtvCheckoutSteps.js +++ b/client/app/queue/mtv/checkout/mtvCheckoutSteps.js @@ -6,7 +6,9 @@ export const views = { }; // This might be more elegantly modeled w/ a finite state machine lib like xstate -export const getSteps = ({ caseType, vacateType }) => { +export const getSteps = (appeal = {}) => { + const { caseType = '', vacateType = '' } = appeal; + switch (vacateType?.toLowerCase()) { case 'straight_vacate': return ['review_vacatures', 'submit']; diff --git a/client/package.json b/client/package.json index 9e36b57290d..6c3890c7291 100644 --- a/client/package.json +++ b/client/package.json @@ -107,10 +107,10 @@ "pluralize": "^7.0.0", "prop-types": "^15.7.2", "rc-collapse": "^1.11.8", - "react": "^16.13.1", + "react": "18.2.0", "react-copy-to-clipboard": "^5.0.1", "react-csv": "^1.1.1", - "react-dom": "^16.13.1", + "react-dom": "18.2.0", "react-draft-wysiwyg": "^1.12.11", "react-highlight-words": "^0.8.0", "react-hook-form": "^6.14.2", diff --git a/client/yarn.lock b/client/yarn.lock index d83474f96f3..83341067d6a 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -14036,15 +14036,13 @@ react-docgen@^5.0.0: node-dir "^0.1.10" strip-indent "^3.0.0" -react-dom@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" - integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== +react-dom@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.23.0" react-dom@^16.9.0: version "16.9.0" @@ -14373,14 +14371,12 @@ react-virtualized@^9.21.2: prop-types "^15.6.0" react-lifecycles-compat "^3.0.4" -react@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" react@^16.9.0: version "16.9.0" @@ -15184,13 +15180,12 @@ scheduler@^0.18.0: loose-envify "^1.1.0" object-assign "^4.1.1" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.23.0: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" schema-utils@2.7.0, schema-utils@^2.6.5, schema-utils@^2.7.0: version "2.7.0" diff --git a/spec/feature/case_distribution_levers/admin_ui_spec.rb b/spec/feature/case_distribution_levers/admin_ui_spec.rb index cb7a08c526d..4b00c0ca7c7 100644 --- a/spec/feature/case_distribution_levers/admin_ui_spec.rb +++ b/spec/feature/case_distribution_levers/admin_ui_spec.rb @@ -95,16 +95,18 @@ step "cancelling lever changes resets values" do # Capybara locally is not setting clearing the field prior to entering the new value so fill with "" - fill_in ama_direct_reviews_field, with: "" + clear_field_completely(ama_direct_reviews_field) + clear_field_completely(alternative_batch_size_field) - fill_in ama_direct_reviews_field, with: "123" + set_field_value_with_delay(ama_direct_reviews_field, "123") expect(page).to have_field(ama_direct_reviews_field, with: "123") + click_cancel_button expect(page).to have_field(ama_direct_reviews_field, with: "365") - fill_in ama_direct_reviews_field, with: "123" - fill_in alternative_batch_size_field, with: "" - fill_in alternative_batch_size_field, with: "12" + set_field_value_with_delay(ama_direct_reviews_field, "123") + set_field_value_with_delay(alternative_batch_size_field, "12") + expect(page).to have_field(ama_direct_reviews_field, with: "123") expect(page).to have_field(alternative_batch_size_field, with: "12") @@ -114,8 +116,12 @@ end step "cancelling changes on confirm modal returns user to page without resetting the values in the fields" do - fill_in ama_direct_reviews_field, with: "123" - fill_in alternative_batch_size_field, with: "13" + clear_field_completely(ama_direct_reviews_field) + clear_field_completely(alternative_batch_size_field) + + set_field_value_with_delay(ama_direct_reviews_field, "123") + set_field_value_with_delay(alternative_batch_size_field, "13") + expect(page).to have_field(ama_direct_reviews_field, with: "123") expect(page).to have_field(alternative_batch_size_field, with: "13") @@ -143,9 +149,11 @@ expect(find("#lever-history-table").has_no_content?("300 days")).to eq(true) # Change two levers at once to satisfy a previously separate test - fill_in ama_direct_reviews_field, with: "" - fill_in ama_direct_reviews_field, with: "123" - fill_in ama_evidence_submissions_field, with: "456" + clear_field_completely(ama_direct_reviews_field) + clear_field_completely(ama_evidence_submissions_field) + + set_field_value_with_delay(ama_direct_reviews_field, "123") + set_field_value_with_delay(ama_evidence_submissions_field, "456") click_save_button click_modal_confirm_button expect(page).to have_content(COPY::CASE_DISTRIBUTION_SUCCESS_BANNER_TITLE) @@ -165,9 +173,13 @@ expect(page).to have_field("#{batch_size_per_attorney}-field", readonly: false) expect(page).to have_field("#{request_more_cases_minimum}-field", readonly: false) - fill_in "#{alternate_batch_size}-field", with: "ABC" - fill_in "#{batch_size_per_attorney}-field", with: "-1" - fill_in "#{request_more_cases_minimum}-field", with: "(*&)" + clear_field_completely("#{batch_size_per_attorney}-field") + clear_field_completely("#{request_more_cases_minimum}-field") + clear_field_completely("#{alternate_batch_size}-field") + + set_field_value_with_delay("#{alternate_batch_size}-field", "ABC") + set_field_value_with_delay("#{batch_size_per_attorney}-field", "-1") + set_field_value_with_delay("#{request_more_cases_minimum}-field", "(*&)") expect(page).to have_field("#{alternate_batch_size}-field", with: "") expect(page).to have_field("#{batch_size_per_attorney}-field", with: "1") @@ -178,9 +190,13 @@ end step "batch size lever section errors clear with valid inputs" do - fill_in "#{alternate_batch_size}-field", with: "42" - fill_in "#{batch_size_per_attorney}-field", with: "32" - fill_in "#{request_more_cases_minimum}-field", with: "25" + clear_field_completely("#{batch_size_per_attorney}-field") + clear_field_completely("#{request_more_cases_minimum}-field") + clear_field_completely("#{alternate_batch_size}-field") + + set_field_value_with_delay("#{alternate_batch_size}-field", "42") + set_field_value_with_delay("#{batch_size_per_attorney}-field", "32") + set_field_value_with_delay("#{request_more_cases_minimum}-field", "25") expect(page).not_to have_content(EMPTY_ERROR_MESSAGE) end @@ -202,4 +218,36 @@ def click_cancel_button def click_modal_cancel_button find("#save-modal-cancel").click end + + # Added this method to handle the delay in setting the field value + # Possibly caused by issue with Chromdirver and Capybara not allowing the field to be cleared before setting the new value + # Issue causes the field to be set with the previous value and the new value + # In addition to each individual character needing being sent with a delay + def set_field_value_with_delay(field_id, value) + area = find("##{field_id}") + area.click + area.native.clear # Clear the field using the native clear method + + # Send keys with delay + value.each_char do |char| + area.send_keys char + sleep 0.1 # 100 ms delay between keystrokes + end + end + + # This method ensures that the field is cleared completely and no lingering values are present + def clear_field_completely(field) + max_attempts = 10 + attempts = 0 + + while find_field(field).value.present? && attempts < max_attempts + find_field(field).set("") + sleep 0.1 # Small delay to allow the field to update + attempts += 1 + end + + if attempts == max_attempts + raise "Failed to clear the field after #{max_attempts} attempts" + end + end end diff --git a/spec/feature/queue/motion_to_vacate_spec.rb b/spec/feature/queue/motion_to_vacate_spec.rb index b9bd44d9551..92150a16f4b 100644 --- a/spec/feature/queue/motion_to_vacate_spec.rb +++ b/spec/feature/queue/motion_to_vacate_spec.rb @@ -670,7 +670,6 @@ def return_to_lit_support(disposition:) expect(page).to have_content(judge.full_name) fill_in "notes", with: "all done" click_on "Submit" - expect(page).to have_content( "Thank you for drafting #{appeal.veteran_full_name}'s decision. It's been "\ "sent to #{judge.full_name} for review." diff --git a/spec/feature/queue/user_organization_spec.rb b/spec/feature/queue/user_organization_spec.rb index d144870e093..16167a51b5a 100644 --- a/spec/feature/queue/user_organization_spec.rb +++ b/spec/feature/queue/user_organization_spec.rb @@ -50,7 +50,9 @@ expect(page).to have_content(format(COPY::USER_MANAGEMENT_PAGE_TITLE, organization.name)) find(".cf-select__control", text: COPY::USER_MANAGEMENT_ADD_USER_TO_ORG_DROPDOWN_TEXT).click - fill_in("Add user", with: user_with_role.css_id) + + set_field_value_with_delay('#add-user', user_with_role.css_id) + expect(page).to have_content(user_with_role.full_name) expect(page).to_not have_content(user_without_role.full_name) @@ -162,3 +164,14 @@ end end end + +def set_field_value_with_delay(selector, value) + area = find(selector) + area.click + + value.each_char do |char| + area.send_keys char + sleep 0.2 + end + area.send_keys :space +end diff --git a/spec/feature/reader/reader_spec.rb b/spec/feature/reader/reader_spec.rb index cb1cda29153..c7e7502a072 100644 --- a/spec/feature/reader/reader_spec.rb +++ b/spec/feature/reader/reader_spec.rb @@ -25,7 +25,7 @@ def add_comment_without_clicking_save(text) click_on "button-AddComment" # text-${pageIndex} is the id of the first page's CommentLayer - page.execute_script("document.querySelectorAll('[id^=\"comment-layer-0\"]')[0].click()") + find('[id^="comment-layer-0"]').click expect(page).to have_css("#addComment", visible: true) diff --git a/spec/models/legacy_appeal_spec.rb b/spec/models/legacy_appeal_spec.rb index ad9b107919d..656220c39ac 100644 --- a/spec/models/legacy_appeal_spec.rb +++ b/spec/models/legacy_appeal_spec.rb @@ -1969,6 +1969,9 @@ context "#contested_claim" do subject { appeal.contested_claim } + before { FeatureToggle.enable!(:indicator_for_contested_claims) } + after { FeatureToggle.disable!(:indicator_for_contested_claims) } + context "when there is no representative" do let(:vacols_case) { create(:case) } it { is_expected.to eq false }