Skip to content

chore: refactor observation fields#1903

Merged
ErikSin merged 42 commits into
developfrom
chore-refactor-observation-fields
Jun 17, 2026
Merged

chore: refactor observation fields#1903
ErikSin merged 42 commits into
developfrom
chore-refactor-observation-fields

Conversation

@ErikSin

@ErikSin ErikSin commented May 21, 2026

Copy link
Copy Markdown
Contributor

closes: #1882

I refactored the Observation Fields to follow our new guidelines.

Previously the ObservationField screen had several nested components. Those nested components had several unnecessary props.

This refactors introduces only one level of nesting, and the nested components have agnostic props instead of passing the entire field object.

I also created several tests. A general integration test that mounts the entire navigation, and tests the observation fields in a normal user interaction. As well as individual tests for each of the smaller components

@ErikSin ErikSin requested a review from cimigree May 21, 2026 23:14
@socket-security

socket-security Bot commented May 21, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

@ErikSin

ErikSin commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

im not sure why, but the front end integration test is failing on CI, but is not faiing locally. Can you try running the tests locally @cimigree

image

@cimigree

cimigree commented May 26, 2026

Copy link
Copy Markdown
Contributor

im not sure why, but the front end integration test is failing on CI, but is not faiing locally. Can you try running the tests locally @cimigree

image

Yes they pass for me too. I can look into it as I review

@ErikSin

ErikSin commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

im not sure why, but the front end integration test is failing on CI, but is not faiing locally. Can you try running the tests locally @cimigree
image

Yes they pass for me too. I can look into it as I review

OK! I think the problem is because we mock/create individual instances of the entire backend in our tests. Since we are doing that, we never install the the backend packages when running the front end CI tests. I was importing the categories for the tests via the backend packages. That is also why it was working locally, because we both have those packages installed locally.

To fix this, i just added the default categories as a dev dep to the front end, and updated the import in the tests

@cimigree cimigree left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor!
Applies the guidelines, as you said.
Sub-components now take agnostic props, logic lives at the top level,
<Question> confusion is gone
Lots of great new integration tests.
Cautions
We will need to make sure that if the default-categories update, we update them in our frontend, i.e., keep them in sync manually.
The tests rely on real categories, which is probably fine, but if they change, the test will need to change.

Questions/ Cautions
The Android hardware back button goes back to the Observation screen instead of the previous question. Is that intentional?
Number now stores a float instead of a string. Are you sure that is merited?

Must Change
I am worried about someone entering incomplete info in the number field accidentally. Just a negative sign, or just a point (.) or -0. and it will be not a number. I can't test what actually happens because we don't have any number fields in the default categories, but will it crash the app? It definitely fails an integration test. It can be easily guarded against with isFinite(and check the input).
And then you can add a test to check those inputs.

And other little changes, see below.

const field = fields.find(val => val.docId === fieldIds[current - 1]);

if (!field) {
// should throw error here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably should remove comment and throw error

<HeaderText variant="header3">
<FormattedFieldProp field={field} propName="label" />
</HeaderText>
{<HeaderText variant="header5">{field.helperText}</HeaderText>}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a wrapper around this? Also, field.helpText can be undefined. So then an empty header text renders leaving a big space. See photo
Maybe do this instead?
{field.helperText && <HeaderText variant="header5">{field.helperText}</HeaderText>}

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know what...i didn't realize that these were superflous wrapper. I think they were created in legacy mapeo with an attempt to guarantee a label, when labels were optional. but now labels are mandatory, so i think i can just get rid of the wrapper completely

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i removed all the wrappers entirely, which means i had to refactor a couple more pages. They were a remnant of the mapeo and they were really convoluted.

5fb9afb

I also wrote several tests related to showing these details in an observation

headerTitle: formatMessage(m.title, {current, total: fieldIds.length}),
headerLeft: props => (
<CustomHeaderLeft headerBackButtonProps={props} onPress={onBackPress} />
<CustomHeaderLeft

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now the android back button goes away from the details and back to the Observation screen (as far as I can tell) when it used to do the same thing as the header back button, that is go back one question. Is this your intention?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.replace(/[^0-9.-]/g, '') // Allow digits, decimal, and negative sign
.replace(/(?!^)-/g, '') // Remove any minus sign that is not at the start
.replace(/(\..*?)\./g, '$1'), // Remove additional decimal points
parseFloat(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseFloat('-'), parseFloat('.'), and parseFloat('-0.') all return NaN It would be good to put something here so incomplete input doesn't save NaN into the observation tags

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow number are annoying. I added so many tests to catch the edge cases! But also the tests made it really easy to find edge cases and determine if it breaks things!

testID="OBS.details-inp"
value={typeof value === 'string' ? value : ''}
testID="OBS.number-inp"
value={typeof tagValue === 'number' ? String(tagValue) : ''}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone is editing an existing observation that has a number field, it will show a blank field because this value used to be saved as a string.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment below!

.replace(/[^0-9.-]/g, '') // Allow digits, decimal, and negative sign
.replace(/(?!^)-/g, '') // Remove any minus sign that is not at the start
.replace(/(\..*?)\./g, '$1'), // Remove additional decimal points
parseFloat(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field used to be stored as a string but now it will be stored as a float. That does make sense, but it is a change. Is that for sure intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handled now. if the value is a string (that can be parsed as a number) it will show the value. and then if they update the value, that string will be updated to a number. 33d8f7d

const details = await screen.findByText('Details');
await user.press(details);

// through manually clicking through the app, I know that this should be a text input

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code comment should be a little less conversational for posterity...Maybe something like the category type Animal's first field is a text input (animal-notes).

fireEvent.changeText(screen.getByDisplayValue(''), '1.2.3');

expect(updateTag).toHaveBeenCalledWith(1.23);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you had added fireEvent.changeText(screen.getByDisplayValue(''), '-');
fireEvent.changeText(screen.getByDisplayValue(''), '.');
It would fail... Once you fix that, you can add that test!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ErikSin ErikSin requested a review from cimigree May 28, 2026 19:48

@cimigree cimigree left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow this is quite a refactor!
With the new config I was able to test numbers!
One thing that worked was using the old version of a number (that was a string) and then switching branches to this new version of number (that is a number). I couldn't even tell it was processing. All worked as expected.

BUT,
I am having 2 issues with numbers.

  1. is that I cannot enter negative numbers using the keyboard given. Not sure if that is a problem. I can paste them in... Not sure if that is expected. So regex makes sense to keep but wondering about use case and needing negative numbers. It may be related to the numeric keyboard type. But I am not sure.
  2. If I have a number in place and then I erase it so that there is no number (because I want to have "no answer") then it doesn't work. The first digit from the previous entry is kept and cannot be cleared. I can type 0 as a work around but I am not sure how intuitive that is and may be misleading or not what a user wants. See video.
Screen_Recording_20260528_180406_CoMapeo.Dev.mp4

Also, my local jest tests are failing.

 FAIL  src/frontend/screens/ComapeoSettings/index.test.tsx (5.355 s)
  ● CoMapeo Settings Screen › opens drawer when header button is pressed

    TypeError: (0 , _reactNative.renderAsync) is not a function

      49 |     onTeardown.push(appProviders.teardown);

And

FAIL  src/frontend/screens/ObservationFields/index.test.tsx
 ● Test suite failed to run

   Cannot find module '@comapeo/default-categories/package.json' from 'src/frontend/screens/ObservationFields/index.test.tsx'

     4 |
     5 | const DEFAULT_CONFIG_PATH = path.join(
   > 6 |   path.dirname(require.resolve('@comapeo/default-categories/package.json')),
       |                        ^
     7 |   'dist/comapeo-default-categories.comapeocat',
     8 | );
     9 |

     at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
     at Object.resolve (src/frontend/screens/ObservationFields/index.test.tsx:6:24)

And a bunch of tests on Browserstack are failing. Not sure if that is flakiness or the tests because it is Observations that are failing.

@awana-lockfile-bot

Copy link
Copy Markdown

package-lock.json changes

Click to toggle table visibility
Name Status Previous Current
@testing-library/react-native UPDATED 13.2.0 13.3.3

@socket-security

socket-security Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​testing-library/​react-native@​13.2.0 ⏵ 13.3.399 +110010099 +1100

View full report

@ErikSin ErikSin requested a review from cimigree June 11, 2026 18:09
@ErikSin

ErikSin commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@cimigree this number issue seems device specific. I have updated the pr to be similar to the previous implementation. Can you try using numbers on develop branch, and then try using numbers on this branch. If they both have the same problem, then lets just merge this pr and open an issue

@cimigree cimigree left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually works fine on develop and on Samsung. Seems like all is fixed. I tested all manually though I see there are so many new and thorough tests. Way to go!

One non blocking comment!

.replace(/[^0-9.-]/g, '') // Allow digits, decimal, and negative sign
.replace(/(?!^)-/g, '') // Remove any minus sign that is not at the start
.replace(/(\..*?)\./g, '$1') // Remove additional decimal points
// .replace(/^(-?)\./, '$10.') // Prefix bare decimal with 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line that you commented out was the one that was breaking the functionality on the Samsung. Can you just delete it instead of leaving it commented out?

@ErikSin ErikSin merged commit 3576633 into develop Jun 17, 2026
10 of 12 checks passed
@ErikSin ErikSin deleted the chore-refactor-observation-fields branch June 17, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor Observation Fields

2 participants