From e597211f709eb4bdaa0dc26f23cbe9b491ab19ed Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Wed, 12 Apr 2023 14:50:35 -0600 Subject: [PATCH 1/3] Fix chartjs warnings Signed-off-by: Taylor Smock --- .../src/components/projectDetail/timeline.js | 19 +++++++++++-- .../projectStats/contributorsStats.js | 14 ++++++++-- .../teamsAndOrgs/tasksStatsChart.js | 28 ++++++++----------- frontend/src/utils/chart.js | 24 ++++++++++++++++ 4 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 frontend/src/utils/chart.js diff --git a/frontend/src/components/projectDetail/timeline.js b/frontend/src/components/projectDetail/timeline.js index a62fde898a..291cb4bf5d 100644 --- a/frontend/src/components/projectDetail/timeline.js +++ b/frontend/src/components/projectDetail/timeline.js @@ -5,19 +5,29 @@ import { PointElement, LinearScale, CategoryScale, - TimeScale, + TimeSeriesScale, Legend, Tooltip, } from 'chart.js'; import { Line } from 'react-chartjs-2'; +import 'chartjs-adapter-date-fns'; import { useIntl } from 'react-intl'; import messages from './messages'; import { formatTimelineData, formatTimelineTooltip } from '../../utils/formatChartJSData'; import { CHART_COLOURS } from '../../config'; import { useTimeDiff } from '../../hooks/UseTimeDiff'; +import { xAxisTimeSeries } from '../../utils/chart'; -ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, TimeScale, Legend, Tooltip); +ChartJS.register( + LineElement, + PointElement, + LinearScale, + CategoryScale, + TimeSeriesScale, + Legend, + Tooltip, +); export default function ProjectTimeline({ tasksByDay }: Object) { const intl = useIntl(); @@ -41,7 +51,10 @@ export default function ProjectTimeline({ tasksByDay }: Object) { callbacks: { label: (context) => formatTimelineTooltip(context, true) }, }, }, - scales: { xAxes: [{ type: 'time', time: { unit: unit } }] }, + scales: { + y: { ticks: { beginAtZero: true } }, + x: { ...xAxisTimeSeries(unit) }, + }, }} /> ); diff --git a/frontend/src/components/projectStats/contributorsStats.js b/frontend/src/components/projectStats/contributorsStats.js index 1d6aa41bb9..f67a306f2d 100644 --- a/frontend/src/components/projectStats/contributorsStats.js +++ b/frontend/src/components/projectStats/contributorsStats.js @@ -1,6 +1,15 @@ import React from 'react'; -import { Chart as ChartJS, ArcElement, BarElement, CategoryScale, LinearScale } from 'chart.js'; import { Doughnut, Bar } from 'react-chartjs-2'; +import { + Chart as ChartJS, + ArcElement, + BarElement, + CategoryScale, + Legend, + LinearScale, + Title, + Tooltip, +} from 'chart.js'; import { FormattedMessage, useIntl } from 'react-intl'; import messages from './messages'; @@ -10,9 +19,8 @@ import { formatChartData, formatTooltip } from '../../utils/formatChartJSData'; import { useContributorStats } from '../../hooks/UseContributorStats'; import { StatsCardContent } from '../statsCard'; -ChartJS.register(ArcElement, BarElement, CategoryScale, LinearScale); - export default function ContributorsStats({ contributors }) { + ChartJS.register(BarElement, CategoryScale, Legend, LinearScale, Title, Tooltip, ArcElement); const intl = useIntl(); const stats = useContributorStats(contributors); const getUserLevelLabel = (level) => intl.formatMessage(userMessages[`mapperLevel${level}`]); diff --git a/frontend/src/components/teamsAndOrgs/tasksStatsChart.js b/frontend/src/components/teamsAndOrgs/tasksStatsChart.js index 22538160b0..ca69e1584b 100644 --- a/frontend/src/components/teamsAndOrgs/tasksStatsChart.js +++ b/frontend/src/components/teamsAndOrgs/tasksStatsChart.js @@ -6,7 +6,7 @@ import { BarElement, Tooltip, Legend, - TimeScale, + TimeSeriesScale, } from 'chart.js'; import { Bar } from 'react-chartjs-2'; import 'chartjs-adapter-date-fns'; @@ -16,8 +16,9 @@ import messages from '../projectDetail/messages'; import { CHART_COLOURS } from '../../config'; import { useTimeDiff } from '../../hooks/UseTimeDiff'; import { formatTasksStatsData, formatTimelineTooltip } from '../../utils/formatChartJSData'; +import { xAxisTimeSeries } from '../../utils/chart'; -ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip, Legend, TimeScale); +ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip, Legend, TimeSeriesScale); const TasksStatsChart = ({ stats }) => { const intl = useIntl(); @@ -39,21 +40,16 @@ const TasksStatsChart = ({ stats }) => { }, }, scales: { - yAxes: [ - { - stacked: true, - ticks: { - beginAtZero: true, - }, + y: { + stacked: true, + ticks: { + beginAtZero: true, }, - ], - xAxes: [ - { - stacked: true, - type: 'time', - time: { unit: unit }, - }, - ], + }, + x: { + stacked: true, + ...xAxisTimeSeries(unit), + }, }, }; return ( diff --git a/frontend/src/utils/chart.js b/frontend/src/utils/chart.js new file mode 100644 index 0000000000..29f9e472da --- /dev/null +++ b/frontend/src/utils/chart.js @@ -0,0 +1,24 @@ +import { enUS } from 'date-fns/locale'; +import { formatISO } from 'date-fns'; +/** + * x axis configuration common between this and {@link ../projectDetail/timeline.js} + * @param unit The base unit for the axis + * @typedef {import('chart.js').ScaleOptionsByType} ScaleOptionsByType + * @returns {ScaleOptionsByType} The options to use for x axis configuration + */ +function xAxisTimeSeries(unit) { + return { + type: 'timeseries', + adapters: { date: { locale: enUS } }, + time: { + unit: unit, + tooltipFormat: enUS.formatLong.date, + }, + ticks: { + source: 'labels', + callback: (value, index, ticks) => formatISO(ticks[index].value, { representation: 'date' }), + }, + }; +} + +export { xAxisTimeSeries }; From 93778522702fbaf45f87587043603813e0c94d45 Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Wed, 7 Feb 2024 07:29:03 -0700 Subject: [PATCH 2/3] Clean up some warnings for project.test.js Signed-off-by: Taylor Smock --- .../src/components/projectDetail/index.js | 30 +++++++++++-- .../src/network/tests/mockData/projects.js | 5 +++ frontend/src/views/tests/project.test.js | 42 ++++++++++--------- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/projectDetail/index.js b/frontend/src/components/projectDetail/index.js index 54a7e83193..183e7f0ad5 100644 --- a/frontend/src/components/projectDetail/index.js +++ b/frontend/src/components/projectDetail/index.js @@ -370,6 +370,29 @@ export const ProjectDetail = (props) => { ); }; +const GeometryPropType = PropTypes.shape({ + type: PropTypes.oneOf([ + 'Point', + 'MultiPoint', + 'LineString', + 'MultiLineString', + 'Polygon', + 'MultiPolygon', + 'GeometryCollection', + ]), + coordinates: PropTypes.array, + geometries: PropTypes.array, +}); +const FeaturePropType = PropTypes.shape({ + type: PropTypes.oneOf(['Feature']), + geometry: GeometryPropType, + properties: PropTypes.object, +}); +const FeatureCollectionPropType = PropTypes.shape({ + type: PropTypes.oneOf(['FeatureCollection']), + features: PropTypes.arrayOf(FeaturePropType).isRequired, +}); + ProjectDetail.propTypes = { project: PropTypes.shape({ projectId: PropTypes.number, @@ -393,10 +416,11 @@ ProjectDetailMap.propTypes = { areaOfInterest: PropTypes.object, priorityAreas: PropTypes.arrayOf(PropTypes.object), }).isRequired, - tasks: PropTypes.arrayOf(PropTypes.object), + // Tasks are a GeoJSON FeatureCollection + tasks: FeatureCollectionPropType, navigate: PropTypes.func, type: PropTypes.string, - tasksError: PropTypes.string, + tasksError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), projectLoading: PropTypes.bool, }; @@ -406,7 +430,7 @@ ProjectDetailLeft.propTypes = { shortDescription: PropTypes.string, }), projectId: PropTypes.number, - tasks: PropTypes.arrayOf(PropTypes.object), + tasks: FeatureCollectionPropType, }).isRequired, contributors: PropTypes.arrayOf(PropTypes.object), className: PropTypes.string, diff --git a/frontend/src/network/tests/mockData/projects.js b/frontend/src/network/tests/mockData/projects.js index bd63833d01..c6b676662c 100644 --- a/frontend/src/network/tests/mockData/projects.js +++ b/frontend/src/network/tests/mockData/projects.js @@ -323,6 +323,7 @@ export const taskDetail = (taskId) => ({ export const projectComments = { chat: [ { + id: 1, message: "

@happy_me we do want 'significant' roads that lead to houses. Rule of thumb I use for picking classification is the usage over condition/what it looks like. If it's the main 'path' to one or maybe several homes, I would pick service; even if a vehicle can't drive it, that can be reflected with additional tags, but the road still functions as access to the home(s).

", pictureUrl: @@ -331,6 +332,7 @@ export const projectComments = { username: 'helnershingthapa', }, { + id: 2, message: '

hello world

', pictureUrl: 'https://www.openstreetmap.org/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBNXQ2Q3c9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--fe41f1b2a5d6cf492a7133f15c81f105dec06ff7/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBPZ2h3Ym1jNkZISmxjMmw2WlY5MGIxOXNhVzFwZEZzSGFXbHBhUT09IiwiZXhwIjpudWxsLCJwdXIiOiJ2YXJpYXRpb24ifX0=--058ac785867b32287d598a314311e2253bd879a3/unnamed.webp', @@ -338,6 +340,7 @@ export const projectComments = { username: 'helnershingthapa', }, { + id: 3, message: '

asdadadasdasdasd

', pictureUrl: 'https://www.openstreetmap.org/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBeFJheFE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--a765e2377a288bccae85da6604300251d9de6d39/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVdscGFRPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--1d22b8d446683a272d1a9ff04340453ca7c374b4/bitmoji.jpg', @@ -345,6 +348,7 @@ export const projectComments = { username: 'Hel Nershing Thapa', }, { + id: 4, message: '

test of \ncode block\nhmmm\npreview showed it as a block\nand monospace font\nbut not indented

', pictureUrl: @@ -353,6 +357,7 @@ export const projectComments = { username: 'wireguy', }, { + id: 5, message: '

this is a code\nblock\nshould it\nbe indented\nby 4 space?\nminor...

', pictureUrl: diff --git a/frontend/src/views/tests/project.test.js b/frontend/src/views/tests/project.test.js index 8c0e6fd602..7b02149495 100644 --- a/frontend/src/views/tests/project.test.js +++ b/frontend/src/views/tests/project.test.js @@ -221,27 +221,13 @@ describe('Project Detail Page', () => { Line: () => null, })); - it('should render component details', async () => { - act(() => { - store.dispatch({ type: 'SET_LOCALE', locale: 'es-AR' }); - }); - renderWithRouter( - - - - - , - ); - await waitFor(() => { - expect(screen.getByText(/sample project/i)).toBeInTheDocument(); - expect(screen.getByText(/hello world/i)).toBeInTheDocument(); - }); - }); - - it('should display private project error message', async () => { - setupFaultyHandlers(); + /** + * Set up a ProjectDetailPage given an initial entry; this avoids issues where there is no project id. + * @param {Array} initialEntries The initial entries. This should be in the form of `[projects/:id]`. + */ + function setup(initialEntries) { render( - + { , ); + } + + it('should render component details', async () => { + act(() => { + store.dispatch({ type: 'SET_LOCALE', locale: 'es-AR' }); + }); + setup(['/projects/123']); + await waitFor(() => { + expect(screen.getByText(/sample project/i)).toBeInTheDocument(); + expect(screen.getByText(/hello world/i)).toBeInTheDocument(); + }); + }); + + it('should display private project error message', async () => { + setupFaultyHandlers(); + setup(['/projects/123']); await waitFor(() => expect( From 71b431a4e12f8d2ab27082e120183ca6679ec1a2 Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Thu, 8 Feb 2024 10:49:18 -0700 Subject: [PATCH 3/3] Import utils/chart.js early to avoid test timeouts due to React.lazy calls Signed-off-by: Taylor Smock --- .../src/components/teamsAndOrgs/tests/tasksStats.test.js | 4 ++++ frontend/src/views/tests/project.test.js | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/frontend/src/components/teamsAndOrgs/tests/tasksStats.test.js b/frontend/src/components/teamsAndOrgs/tests/tasksStats.test.js index 7bc261bfa3..ab055371d6 100644 --- a/frontend/src/components/teamsAndOrgs/tests/tasksStats.test.js +++ b/frontend/src/components/teamsAndOrgs/tests/tasksStats.test.js @@ -7,6 +7,10 @@ import { tasksStats } from '../../../network/tests/mockData/tasksStats'; import { TasksStats } from '../tasksStats'; import userEvent from '@testing-library/user-event'; +// This is a late import in a React.lazy call; it takes awhile for date-fns to resolve, so we import it here manually. +// In the event you remove it, please measure test times before ''and'' after removal. +import '../../../utils/chart'; + jest.mock('react-chartjs-2', () => ({ Bar: () => null, })); diff --git a/frontend/src/views/tests/project.test.js b/frontend/src/views/tests/project.test.js index 7b02149495..c7ff487d13 100644 --- a/frontend/src/views/tests/project.test.js +++ b/frontend/src/views/tests/project.test.js @@ -24,6 +24,13 @@ import { setupFaultyHandlers } from '../../network/tests/server'; import { projects } from '../../network/tests/mockData/projects'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; +// This is a late import in a React.lazy call; it takes awhile for date-fns to resolve, so we import it here manually. +// In the event you remove it, please measure test times before ''and'' after removal. +import '../../utils/chart'; + +// scrollTo is not implemented by jsdom; mock to avoid warnings. +window.scrollTo = jest.fn(); + test('CreateProject renders ProjectCreate', async () => { renderWithRouter(