diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1507b79e58..9efd062c3a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -423,7 +423,7 @@ parameters:
default: "mb/TTAHUB-2943/remove-models"
type: string
sandbox_git_branch: # change to feature branch to test deployment
- default: "mb/TTAHUB-2530/update-RTR-objective-form"
+ default: "al-ttahub-2942-fix-number-of-goals"
type: string
prod_new_relic_app_id:
default: "877570491"
diff --git a/frontend/src/components/GoalCards/GoalCards.js b/frontend/src/components/GoalCards/GoalCards.js
index 05dcc948b6..b3e0876b89 100644
--- a/frontend/src/components/GoalCards/GoalCards.js
+++ b/frontend/src/components/GoalCards/GoalCards.js
@@ -31,6 +31,7 @@ function GoalCards({
canMergeGoals,
shouldDisplayMergeSuccess,
dismissMergeSuccess,
+ goalBuckets,
}) {
const history = useHistory();
const [rttapaValidation, setRttapaValidation] = useState(false);
@@ -38,7 +39,6 @@ function GoalCards({
// Goal select check boxes.
const [selectedGoalCheckBoxes, setSelectedGoalCheckBoxes] = useState({});
const [allGoalsChecked, setAllGoalsChecked] = useState(false);
- const [printAllGoals, setPrintAllGoals] = useState(false);
// Close/Suspend Reason Modal.
const [closeSuspendGoalIds, setCloseSuspendGoalIds] = useState([]);
@@ -116,29 +116,35 @@ function GoalCards({
goalsArr.reduce((obj, g) => ({ ...obj, [g.id]: checked }), {})
);
- useEffect(() => {
- const checkValues = Object.values(selectedGoalCheckBoxes);
- if (checkValues.length
- && (checkValues.length === goals.length || checkValues.length === goalsCount)
- && checkValues.every((v) => v === true)) {
- setAllGoalsChecked(true);
- } else if (printAllGoals === true) {
- setPrintAllGoals(false);
- }
- }, [selectedGoalCheckBoxes, allGoalsChecked, printAllGoals, goalsCount, goals.length]);
-
const selectAllGoalCheckboxSelect = (event) => {
const { target: { checked = null } = {} } = event;
+ // Preserve checked goals on other pages.
+ const thisPagesGoalIds = goals.map((g) => g.id);
+ const preservedCheckboxes = Object.keys(selectedGoalCheckBoxes).reduce((obj, key) => {
+ if (!thisPagesGoalIds.includes(parseInt(key, DECIMAL_BASE))) {
+ return { ...obj, [key]: selectedGoalCheckBoxes[key] };
+ }
+ return { ...obj };
+ }, {});
+
if (checked === true) {
- setSelectedGoalCheckBoxes(makeGoalCheckboxes(goals, true));
+ setSelectedGoalCheckBoxes({ ...makeGoalCheckboxes(goals, true), ...preservedCheckboxes });
+ } else {
+ setSelectedGoalCheckBoxes({ ...makeGoalCheckboxes(goals, false), ...preservedCheckboxes });
+ }
+ };
+
+ // Check if all goals on the page are checked.
+ useEffect(() => {
+ const goalIds = goals.map((g) => g.id);
+ const countOfCheckedOnThisPage = goalIds.filter((id) => selectedGoalCheckBoxes[id]).length;
+ if (goals.length === countOfCheckedOnThisPage) {
setAllGoalsChecked(true);
} else {
- setSelectedGoalCheckBoxes(makeGoalCheckboxes(goals, false));
setAllGoalsChecked(false);
- setPrintAllGoals(false);
}
- };
+ }, [goals, selectedGoalCheckBoxes]);
const handleGoalCheckboxSelect = (event) => {
const { target: { checked = null, value = null } = {} } = event;
@@ -149,10 +155,9 @@ function GoalCards({
}
};
- const checkAllGoals = () => {
- const allIdCheckBoxes = allGoalIds.reduce((obj, g) => ({ ...obj, [g]: true }), {});
+ const checkAllGoals = (isClear) => {
+ const allIdCheckBoxes = allGoalIds.reduce((obj, g) => ({ ...obj, [g]: !isClear }), {});
setSelectedGoalCheckBoxes(allIdCheckBoxes);
- setPrintAllGoals(true);
};
const numberOfSelectedGoals = Object.values(selectedGoalCheckBoxes).filter((g) => g).length;
@@ -164,14 +169,14 @@ function GoalCards({
const selectedGoalIdsButNumerical = selectedCheckBoxes.map((id) => parseInt(id, DECIMAL_BASE));
const draftSelectedRttapa = goals.filter((g) => selectedGoalIdsButNumerical.includes(g.id) && g.goalStatus === 'Draft').map((g) => g.id);
- const allSelectedGoalIds = (() => {
+ const allSelectedPageGoalIds = (() => {
const selection = goals.filter((g) => selectedGoalCheckBoxes[g.id]);
- return selection.map((g) => g.ids).flat();
+ return selection.map((g) => g.id);
})();
const rttapaLink = (() => {
if (selectedCheckBoxes && selectedCheckBoxes.length) {
- const selectedGoalIdsQuery = allSelectedGoalIds.map((id) => `goalId[]=${encodeURIComponent(id)}`).join('&');
+ const selectedGoalIdsQuery = allSelectedPageGoalIds.map((id) => `goalId[]=${encodeURIComponent(id)}`).join('&');
return `/recipient-tta-records/${recipientId}/region/${regionId}/rttapa/new?${selectedGoalIdsQuery}`;
}
@@ -232,7 +237,7 @@ function GoalCards({
allGoalsChecked={allGoalsChecked}
selectAllGoalCheckboxSelect={selectAllGoalCheckboxSelect}
selectAllGoals={checkAllGoals}
- selectedGoalIds={allSelectedGoalIds}
+ pageSelectedGoalIds={allSelectedPageGoalIds}
perPageChange={perPageChange}
pageGoalIds={goals.map((g) => g.id)}
showRttapaValidation={showRttapaValidation}
@@ -241,6 +246,8 @@ function GoalCards({
canMergeGoals={canMergeGoals}
shouldDisplayMergeSuccess={shouldDisplayMergeSuccess}
dismissMergeSuccess={dismissMergeSuccess}
+ goalBuckets={goalBuckets}
+ allSelectedGoalIds={selectedGoalCheckBoxes}
/>
{goals.map((goal, index) => (
@@ -291,6 +298,10 @@ GoalCards.propTypes = {
canMergeGoals: PropTypes.bool.isRequired,
shouldDisplayMergeSuccess: PropTypes.bool,
dismissMergeSuccess: PropTypes.func.isRequired,
+ goalBuckets: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ goalIds: PropTypes.arrayOf(PropTypes.number),
+ })).isRequired,
};
GoalCards.defaultProps = {
diff --git a/frontend/src/components/GoalCards/GoalDataController.js b/frontend/src/components/GoalCards/GoalDataController.js
index aaa8c74bab..d50dc898ed 100644
--- a/frontend/src/components/GoalCards/GoalDataController.js
+++ b/frontend/src/components/GoalCards/GoalDataController.js
@@ -132,7 +132,9 @@ function GoalDataController({
query,
mergedGoals || [],
);
- setData(response);
+ const rolledUpGoalIds = response.allGoalIds.map((goal) => goal.id);
+ const goalBuckets = response.allGoalIds;
+ setData({ ...response, allGoalIds: rolledUpGoalIds, goalBuckets });
setError('');
// display success message if we have merged goals
setShouldDisplayMergedSuccess((mergedGoals && mergedGoals.length > 0));
@@ -259,6 +261,7 @@ function GoalDataController({
canMergeGoals={canMergeGoals}
shouldDisplayMergeSuccess={shouldDisplayMergeSuccess}
dismissMergeSuccess={dismissMergeSuccess}
+ goalBuckets={data.goalBuckets}
/>
diff --git a/frontend/src/components/GoalCards/GoalsCardsHeader.js b/frontend/src/components/GoalCards/GoalsCardsHeader.js
index ad0c33b153..1086b88a46 100644
--- a/frontend/src/components/GoalCards/GoalsCardsHeader.js
+++ b/frontend/src/components/GoalCards/GoalsCardsHeader.js
@@ -30,7 +30,7 @@ export default function GoalCardsHeader({
allGoalsChecked,
selectAllGoalCheckboxSelect,
selectAllGoals,
- selectedGoalIds,
+ pageSelectedGoalIds,
perPageChange,
pageGoalIds,
showRttapaValidation,
@@ -38,6 +38,8 @@ export default function GoalCardsHeader({
canMergeGoals,
shouldDisplayMergeSuccess,
dismissMergeSuccess,
+ allSelectedGoalIds,
+ goalBuckets,
}) {
const [goalMergeGroups, setGoalMergeGroups] = useState([]);
const history = useHistory();
@@ -71,8 +73,22 @@ export default function GoalCardsHeader({
const showAddNewButton = hasActiveGrants && hasButtonPermissions;
const onPrint = () => {
+ // See if we have goals selected.
+ let goalsToPrint = Object.keys(allSelectedGoalIds).filter(
+ (key) => allSelectedGoalIds[key],
+ ).map((key) => parseInt(key, DECIMAL_BASE));
+
+ // If we don't just print the page.
+ if (!goalsToPrint.length) {
+ goalsToPrint = pageGoalIds;
+ }
+ // Get all the goals and associated goals from the buckets.
+ goalsToPrint = goalBuckets.filter(
+ (bucket) => goalsToPrint.includes(bucket.id),
+ ).map((bucket) => bucket.goalIds).flat();
+
history.push(`/recipient-tta-records/${recipientId}/region/${regionId}/rttapa/print${window.location.search}`, {
- sortConfig, selectedGoalIds: !selectedGoalIds.length ? pageGoalIds : selectedGoalIds,
+ sortConfig, selectedGoalIds: goalsToPrint,
});
};
@@ -89,6 +105,9 @@ export default function GoalCardsHeader({
return null;
})();
+ const hasGoalsSelected = pageSelectedGoalIds ? pageSelectedGoalIds.length > 0 : false;
+ const showClearAllAlert = numberOfSelectedGoals === count;
+
return (
@@ -199,7 +218,7 @@ export default function GoalCardsHeader({
className="display-flex flex-align-center margin-left-3 margin-y-0"
onClick={onPrint}
>
- {`Preview and print ${selectedGoalIds.length > 0 ? 'selected' : ''}`}
+ {`Preview and print ${hasGoalsSelected ? 'selected' : ''}`}
@@ -222,16 +241,20 @@ export default function GoalCardsHeader({
)}
{
- !showRttapaValidation && allGoalsChecked && (numberOfSelectedGoals !== count)
+ !showRttapaValidation && allGoalsChecked
? (
- {`All ${numberOfSelectedGoals} goals on this page are selected.`}
+ {showClearAllAlert
+ ? `All ${count} goals are selected.`
+ : `All ${pageSelectedGoalIds.length} goals on this page are selected.`}
)
@@ -287,7 +310,7 @@ GoalCardsHeader.propTypes = {
allGoalsChecked: PropTypes.bool,
numberOfSelectedGoals: PropTypes.number,
selectAllGoals: PropTypes.func,
- selectedGoalIds: PropTypes.arrayOf(PropTypes.string).isRequired,
+ pageSelectedGoalIds: PropTypes.arrayOf(PropTypes.number).isRequired,
perPageChange: PropTypes.func.isRequired,
pageGoalIds: PropTypes.oneOfType(
[PropTypes.arrayOf(PropTypes.number), PropTypes.number],
@@ -297,6 +320,11 @@ GoalCardsHeader.propTypes = {
canMergeGoals: PropTypes.bool.isRequired,
shouldDisplayMergeSuccess: PropTypes.bool,
dismissMergeSuccess: PropTypes.func.isRequired,
+ allSelectedGoalIds: PropTypes.shape({ id: PropTypes.bool }).isRequired,
+ goalBuckets: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ goals: PropTypes.arrayOf(PropTypes.number),
+ })).isRequired,
};
GoalCardsHeader.defaultProps = {
diff --git a/frontend/src/components/GoalCards/__tests__/GoalCards.js b/frontend/src/components/GoalCards/__tests__/GoalCards.js
index 7edad74bdc..31fb57d644 100644
--- a/frontend/src/components/GoalCards/__tests__/GoalCards.js
+++ b/frontend/src/components/GoalCards/__tests__/GoalCards.js
@@ -36,7 +36,7 @@ const defaultUser = {
const baseGoals = [{
id: 4598,
- ids: [4598],
+ ids: [4598, 4599],
goalStatus: 'In Progress',
createdOn: '2021-06-15',
goalText: 'This is goal text 1.',
@@ -220,6 +220,7 @@ const setGoals = jest.fn();
const history = createMemoryHistory();
const renderTable = ({ goals, goalsCount, allGoalIds = null }, user, hasActiveGrants = true) => {
+ const goalBuckets = !goals ? [] : goals.map((g) => ({ id: g.id, goalIds: g.ids }));
render(
@@ -246,6 +247,7 @@ const renderTable = ({ goals, goalsCount, allGoalIds = null }, user, hasActiveGr
allGoalIds={allGoalIds || goals.map((g) => g.id)}
shouldDisplayMergeSuccess={false}
dismissMergeSuccess={jest.fn()}
+ goalBuckets={goalBuckets}
/>
@@ -489,6 +491,27 @@ describe('Goals Table', () => {
expect(screen.queryByText(/7 selected/i)).toBeNull();
});
+ it('Shows the clear selection button and clears when clicked', async () => {
+ const selectAll = await screen.findByRole('checkbox', { name: /deselect all goals/i });
+ fireEvent.click(selectAll);
+ expect(await screen.findByText(/6 selected/i)).toBeVisible();
+
+ const selectAllPages = await screen.findByRole('button', { name: /select all 7 goals/i });
+ fireEvent.click(selectAllPages);
+
+ expect(screen.queryByText(/7 selected/i)).toBeVisible();
+
+ const clearSelection = await screen.findByRole('button', { name: /clear selection/i });
+ fireEvent.click(clearSelection);
+
+ expect(screen.queryByText(/7 selected/i)).toBeNull();
+ // verify all check boxes are unchecked.
+ const checkBoxes = screen.queryAllByTestId('selectGoalTestId');
+ checkBoxes.forEach((checkBox) => {
+ expect(checkBox.checked).toBe(false);
+ });
+ });
+
it('Deselect via pill', async () => {
const selectAll = await screen.findByRole('checkbox', { name: /deselect all goals/i });
fireEvent.click(selectAll);
@@ -604,6 +627,35 @@ describe('Goals Table', () => {
expect(history.push).toHaveBeenCalled();
});
+
+ it('calls print passing all goal ids on the page', async () => {
+ // print goals
+ const printButton = await screen.findByRole('button', { name: /Preview and print/i });
+ userEvent.click(printButton);
+ expect(history.push).toHaveBeenCalledWith('/recipient-tta-records/1000/region/1/rttapa/print', {
+ selectedGoalIds: [4598, 4599, 65479],
+ sortConfig: {
+ activePage: 1, direction: 'asc', offset: 0, sortBy: 'goalStatus',
+ },
+ });
+ });
+
+ it('calls print passing all selected goal ids', async () => {
+ // print goals
+ const printButton = await screen.findByRole('button', { name: /Preview and print/i });
+
+ // select the checkbox with the value of 4598.
+ const checkBox = screen.queryAllByTestId('selectGoalTestId')[0];
+ fireEvent.click(checkBox);
+
+ userEvent.click(printButton);
+ expect(history.push).toHaveBeenCalledWith('/recipient-tta-records/1000/region/1/rttapa/print', {
+ selectedGoalIds: [4598, 4599],
+ sortConfig: {
+ activePage: 1, direction: 'asc', offset: 0, sortBy: 'goalStatus',
+ },
+ });
+ });
});
describe('Context Menu with Different User Permissions', () => {
diff --git a/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js b/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js
index 60009aaac8..92352568d0 100644
--- a/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js
+++ b/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js
@@ -145,17 +145,37 @@ describe('Goals and Objectives', () => {
fetchMock.reset();
// Default.
const goalsUrl = `/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10&createDate.win=${yearToDate}`;
- fetchMock.get(goalsUrl, { count: 1, goalRows: goals, statuses: defaultStatuses });
+ fetchMock.get(goalsUrl, {
+ count: 1,
+ goalRows: goals,
+ statuses: defaultStatuses,
+ allGoalIds: [
+ { id: goals[0].id, goalIds: goals[0].ids },
+ ],
+ });
// Filters Status.
const filterStatusUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10&status.in[]=Not%20started';
fetchMock.get(filterStatusUrl, {
- count: 1, goalRows: filterStatusGoals, statuses: defaultStatuses,
+ count: 1,
+ goalRows: filterStatusGoals,
+ statuses: defaultStatuses,
+ allGoalIds: [
+ { id: filterStatusGoals[0].id, goalIds: filterStatusGoals[0].ids },
+ ],
});
// No Filters.
const noFilterUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10';
- fetchMock.get(noFilterUrl, { count: 2, goalRows: noFilterGoals, statuses: defaultStatuses });
+ fetchMock.get(noFilterUrl, {
+ count: 2,
+ goalRows: noFilterGoals,
+ statuses: defaultStatuses,
+ allGoalIds: [
+ { id: noFilterGoals[0].id, goalIds: noFilterGoals[0].ids },
+ { id: noFilterGoals[1].id, goalIds: noFilterGoals[1].ids },
+ ],
+ });
fetchMock.get(
'/api/communication-logs/region/1/recipient/401?sortBy=communicationDate&direction=desc&offset=0&limit=5&format=json&purpose.in[]=RTTAPA%20updates&purpose.in[]=RTTAPA%20Initial%20Plan%20%2F%20New%20Recipient',
@@ -243,7 +263,7 @@ describe('Goals and Objectives', () => {
fetchMock.get(
'/api/communication-logs/region/1/recipient/401?sortBy=communicationDate&direction=desc&offset=0&limit=5&format=json&purpose.in[]=RTTAPA%20updates&purpose.in[]=RTTAPA%20Initial%20Plan%20%2F%20New%20Recipient',
- { rows: [], count: 0 },
+ { rows: [], count: 0, allGoalIds: [] },
);
const response = [{
@@ -262,7 +282,12 @@ describe('Goals and Objectives', () => {
];
const noFilterUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10';
- fetchMock.get(noFilterUrl, { count: 2, goalRows: response, statuses: defaultStatuses });
+ fetchMock.get(noFilterUrl, {
+ count: 2,
+ goalRows: response,
+ statuses: defaultStatuses,
+ allGoalIds: [{ id: 4598, goalIds: [4598] }],
+ });
act(() => renderGoalsAndObjectives());
@@ -305,6 +330,19 @@ describe('Goals and Objectives', () => {
{ ...goals[0], id: 3 },
],
statuses: defaultStatuses,
+ allGoalIds: [{
+ id: 1,
+ goalIds: [1],
+ },
+ {
+ id: 2,
+ goalIds: [2],
+ },
+ {
+ id: 3,
+ goalIds: [3],
+ },
+ ],
});
act(() => renderGoalsAndObjectives([1]));
// If api request contains 3 we know it included the desired sort.
@@ -331,7 +369,7 @@ describe('Goals and Objectives', () => {
fetchMock.get(
'/api/communication-logs/region/1/recipient/401?sortBy=communicationDate&direction=desc&offset=0&limit=5&format=json&purpose.in[]=RTTAPA%20updates&purpose.in[]=RTTAPA%20Initial%20Plan%20%2F%20New%20Recipient',
- { rows: [], count: 0 },
+ { rows: [], count: 0, allGoalIds: [] },
);
const goalToUse = {
id: 1,
@@ -348,10 +386,12 @@ describe('Goals and Objectives', () => {
};
const goalCount = 60;
const goalsToDisplay = [];
+ const allGoalIds = [];
// eslint-disable-next-line no-plusplus
for (let i = 1; i <= goalCount; i++) {
const goalText = `This is goal text ${i}.`;
goalsToDisplay.push({ ...goalToUse, id: i, goalText });
+ allGoalIds.push({ id: i, goalIds: [i] });
}
const noFilterUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10';
fetchMock.get(noFilterUrl,
@@ -359,6 +399,7 @@ describe('Goals and Objectives', () => {
count: goalCount,
goalRows: goalsToDisplay.slice(0, 10),
statuses: defaultStatuses,
+ allGoalIds,
});
// Render.
@@ -376,6 +417,7 @@ describe('Goals and Objectives', () => {
count: goalCount,
goalRows: goalsToDisplay.slice(0, 25),
statuses: defaultStatuses,
+ allGoalIds,
});
const perPageDropDown = await screen.findByRole('combobox', { name: /select goals per page/i });
userEvent.selectOptions(perPageDropDown, '25');
@@ -385,4 +427,146 @@ describe('Goals and Objectives', () => {
goalsPerPage = screen.queryAllByTestId('goalCard');
expect(goalsPerPage.length).toBe(25);
});
+
+ it('respects select all on a per page basis', async () => {
+ const goalUrl = '/api/recipient/401/region/1/goals?sortBy=createdOn&sortDir=desc&offset=0&limit=10';
+ fetchMock.get(goalUrl, {
+ count: 12,
+ goalRows: [
+ { ...goals[0], id: 1 },
+ { ...goals[0], id: 2 },
+ { ...goals[0], id: 3 },
+ { ...goals[0], id: 4 },
+ { ...goals[0], id: 5 },
+ { ...goals[0], id: 6 },
+ { ...goals[0], id: 7 },
+ { ...goals[0], id: 8 },
+ { ...goals[0], id: 9 },
+ { ...goals[0], id: 10 },
+ ],
+ statuses: defaultStatuses,
+ allGoalIds: [{
+ id: 1,
+ goalIds: [1],
+ },
+ {
+ id: 2,
+ goalIds: [2],
+ },
+ {
+ id: 3,
+ goalIds: [3],
+ },
+ {
+ id: 4,
+ goalIds: [4],
+ },
+ {
+ id: 5,
+ goalIds: [5],
+ },
+ {
+ id: 6,
+ goalIds: [6],
+ },
+ {
+ id: 7,
+ goalIds: [7],
+ },
+ {
+ id: 8,
+ goalIds: [8],
+ },
+ {
+ id: 9,
+ goalIds: [9],
+ },
+ {
+ id: 10,
+ goalIds: [10],
+ },
+ ],
+ });
+ act(() => renderGoalsAndObjectives([1]));
+ expect(await screen.findByText(/1-10 of 12/i)).toBeVisible();
+
+ // Select All.
+ const selectAll = await screen.findByRole('checkbox', { name: /select all goals/i });
+ userEvent.click(selectAll);
+
+ // Assert all are selected.
+ const checkboxes = screen.queryAllByRole('checkbox', { name: /select goal/i });
+ expect(checkboxes.length).toBe(10);
+ checkboxes.forEach((checkbox) => {
+ expect(checkbox).toBeChecked();
+ });
+
+ // Shows 10 selected.
+ expect(await screen.findByText(/10 selected/i)).toBeVisible();
+
+ // Change per page.
+ const goalUrlMore = '/api/recipient/401/region/1/goals?sortBy=createdOn&sortDir=desc&offset=10&limit=10';
+ fetchMock.get(goalUrlMore, {
+ count: 12,
+ goalRows: [
+ { ...goals[0], id: 11 },
+ { ...goals[0], id: 12 },
+ ],
+ statuses: defaultStatuses,
+ allGoalIds: [{
+ id: 11,
+ goalIds: [11],
+ },
+ {
+ id: 12,
+ goalIds: [12],
+ },
+ ],
+ });
+
+ // Click page 2.
+ const pageTwo = await screen.findByRole('link', { name: /go to page number 2/i });
+ userEvent.click(pageTwo);
+
+ expect(await screen.findByText(/11-12 of 12/i)).toBeVisible();
+
+ // Shows 10 selected.
+ expect(await screen.findByText(/10 selected/i)).toBeVisible();
+
+ // Assert all selected is NOT checked.
+ const selectAllNext = await screen.findByRole('checkbox', { name: /select all goals/i });
+ expect(selectAllNext).not.toBeChecked();
+
+ // Get all the checkboxes.
+ const checkboxesNext = screen.queryAllByRole('checkbox', { name: /select goal/i });
+ expect(checkboxesNext.length).toBe(2);
+
+ // Check the second one.
+ userEvent.click(checkboxesNext[1]);
+
+ // Shows 11 selected.
+ expect(await screen.findByText(/11 selected/i)).toBeVisible();
+
+ // Select All.
+ userEvent.click(selectAllNext);
+
+ // Assert all are selected.
+ const checkboxesNextAll = screen.queryAllByRole('checkbox', { name: /select goal/i });
+ expect(checkboxesNextAll.length).toBe(2);
+ checkboxesNextAll.forEach((checkbox) => {
+ expect(checkbox).toBeChecked();
+ });
+
+ // Shows 12 selected.
+ expect(await screen.findByText(/12 selected/i)).toBeVisible();
+
+ // Uncheck the second checkbox.
+ userEvent.click(checkboxesNext[1]);
+
+ // Assert the select all check box is not checked.
+ expect(selectAllNext).not.toBeChecked();
+
+ // Shows 11 selected.
+ expect(await screen.findByText(/11 selected/i)).toBeVisible();
+ });
});
diff --git a/src/services/recipient.js b/src/services/recipient.js
index e82efd901a..dea3aa3633 100644
--- a/src/services/recipient.js
+++ b/src/services/recipient.js
@@ -894,12 +894,21 @@ export async function getGoalsByActivityRecipient(
},
});
+ // For checkbox selection we only need the primary goal id.
+ const rolledUpGoalIds = r.goalRows.map((goal) => {
+ const bucket = {
+ id: goal.id,
+ goalIds: goal.ids,
+ };
+ return bucket;
+ });
+
if (limitNum) {
return {
count: r.goalRows.length,
goalRows: r.goalRows.slice(offSetNum, offSetNum + limitNum),
statuses,
- allGoalIds,
+ allGoalIds: rolledUpGoalIds,
};
}
@@ -907,7 +916,7 @@ export async function getGoalsByActivityRecipient(
count: r.goalRows.length,
goalRows: r.goalRows.slice(offSetNum),
statuses,
- allGoalIds,
+ allGoalIds: rolledUpGoalIds,
};
}
diff --git a/src/services/recipient.test.js b/src/services/recipient.test.js
index 0c0888b8ee..ddc9c7ccc9 100644
--- a/src/services/recipient.test.js
+++ b/src/services/recipient.test.js
@@ -874,8 +874,13 @@ describe('Recipient DB service', () => {
});
it('properly de-duplicates based on responses', async () => {
- const { goalRows } = await getGoalsByActivityRecipient(recipient.id, region, {});
+ const { goalRows, allGoalIds } = await getGoalsByActivityRecipient(recipient.id, region, {});
expect(goalRows.length).toBe(3);
+ expect(allGoalIds.length).toBe(3);
+
+ const goalWithMultipleIds = allGoalIds.find((g) => g.id === goals[2].id);
+ expect(goalWithMultipleIds).not.toBeNull();
+ expect(goalWithMultipleIds.goalIds).toStrictEqual([goals[2].id, goals[1].id]);
const doubler = goalRows.find((r) => r.responsesForComparison === 'not sure,dont have to');
expect(doubler).toBeTruthy();
@@ -896,8 +901,9 @@ describe('Recipient DB service', () => {
goals[0].destroy();
goals[3].destroy();
- const { goalRows } = await getGoalsByActivityRecipient(recipient.id, region, {});
+ const { goalRows, allGoalIds } = await getGoalsByActivityRecipient(recipient.id, region, {});
expect(goalRows.length).toBe(1);
+ expect(allGoalIds.length).toBe(1);
// Verify goal 2 and 3 have empty creators/collaborators
expect(goalRows[0].collaborators[0].goalCreator).toBe(undefined);
// Verify goal 2 and 3 are rolled up
@@ -1063,7 +1069,7 @@ describe('Recipient DB service', () => {
expect(goalsForRecord.count).toBe(1);
expect(goalsForRecord.goalRows.length).toBe(1);
- expect(goalsForRecord.allGoalIds.length).toBe(2);
+ expect(goalsForRecord.allGoalIds.length).toBe(1);
expect(goalsForRecord.goalRows.flatMap((g) => g.goalTopics)).toHaveLength(4);
const goal = goalsForRecord.goalRows[0];