Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite answer.js into enketoHelper.ts #1063

Merged

Conversation

Abby-Wheelis
Copy link
Member

This PR is based on Jack's rewrite PR so that I can work off his mocks and hopefully minimize merge conflicts once that branch can be merged.

Addresses one of the rewrites in: ✍ Angular Services needing rewrite

Abby Wheelis added 4 commits October 11, 2023 14:50
moving the methods form answer.js into enketoHelper as a part of the services migration
When testing, I was getting an error from Jest about duplicate modules, one of which was in platforms. This change resolves that error
@Abby-Wheelis
Copy link
Member Author

I have discovered an issue here, when I try to add an activity to a trip or a place in the emulator, that activity does not show up in a list on the card as expected. Hopefully I can find the issue and resolve in the re-write and test process.

Abby Wheelis added 2 commits October 11, 2023 15:58
additional test for filterByNameAndVersion

fake answers have been constructed to be filtered
completely remove answer.js and all references to it, replace references with references to enketoHelper.ts
@Abby-Wheelis
Copy link
Member Author

One of the things that's popping up with testing, is the need for a fake survey. I had been copying part of the document from somewhere like trip-end-survey.json and trying to fill in answers, but I'm not sure that's really working the way I think it is. I wonder if there's a way for me to download the xml for a filled-out survey from a tool like kobotoolbox? I've tried looking at log statements, but it cuts the printing of the xml off in a console.log statement, which makes sense given how long the files can be.

resolveTimestamps is a helper function to saveResponse, but still contains a fair amount of its own logic. Testing the edge cases for this function ensures that it will behave as expected within the larger context
@Abby-Wheelis
Copy link
Member Author

One of the things that's popping up with testing, is the need for a fake survey.

I was able to clear this for the test I was working on in that moment by just copying a part of the survey from the logs.

Now, I'm facing an issue with loading the config, in the process of trying to test resolveLabel. I think my next step is to test _lazyLoadConfig independently to make sure that is working, with the web of calls to storage that I might need to mock (some of them are already mocked). What's odd is that I have tests for filterByNameAndVersion already running, and those depend on _lazyLoadConfig so my problem might be elsewhere.

adding a basic test for _loadLazyConfig, in order to ensure that my mock setup for that works, before moving into testing functions that depend on it
@Abby-Wheelis
Copy link
Member Author

test _lazyLoadConfig independently to make sure that is working, with the web of calls to storage that I might need to mock (some of them are already mocked).

I did this by mocking getDocument in the cordova mocks in order to return a hard-coded of a config with just enough information to have one survey. This works for now, and can be expanded later as needed.

@Abby-Wheelis
Copy link
Member Author

I've moved on try trying to test resolveLabels and to do that I need i18next and MessageFormat. I have been trying to find a way to mock these and allow the tests to access the mocks, but so far have been unsuccessful. Here's some information I found about Manual Mocks in Jest. It seemed like a good place to start, but I have not been able to have the code running in the tests access the mocks, it just seems to fail as undefined for some reason.

currently struggling with i18next and MessageFormat, as I can't get either of those mocked and working
@Abby-Wheelis
Copy link
Member Author

I have discovered an issue here, when I try to add an activity to a trip or a place in the emulator, that activity does not show up in a list on the card as expected. Hopefully I can find the issue and resolve in the re-write and test process.

I have not found a way to fix this yet. I did decide to check on master, and it works there. The other difference between this branch and master is that the demographics survey does not save between logins on this branch.

On service_rewrite_2023, the activities show up after I input them and persist after refreshing the label screen, but don't stay after logging out and back in. The demographic survey responses don't show up after a new login either.

I also checked out JGreenlee:rewrite-services-sept2023, since those changes are in this branch too. Not saving between logins is observed here, but when I input activities they show up, so there are no regressions between service_rewrite_2023 and the end of Jack's changes.

To summarize:

  • the demographic survey no longer saves between logins due to changes between master and service_rewrite_2023
  • in my migration I broke saving the activity survey responses and having them show on the label screen

I can investigate further tomorrow, but I have a better idea now of when the regressions took place.

@JGreenlee
Copy link
Collaborator

JGreenlee commented Oct 13, 2023

On service_rewrite_2023, the activities show up after I input them and persist after refreshing the label screen, but don't stay after logging out and back in. The demographic survey responses don't show up after a new login either.

After talking, we think this is actually the expected behavior. The log out button should clear everything. If we do not "force sync" before logging out, any unsynced data is lost. This is why we have the "Are you sure?" popup explaining this to users before they log out

@Abby-Wheelis
Copy link
Member Author

Abby-Wheelis commented Oct 13, 2023

Some updates after meeting with @JGreenlee this morning to discuss the problems I've been having:

Instead of trying to mock i18next and messageformat, it is better to use the actual packages. To do this we configured i18next in the test file, which should work after I incorporate the changes from React Component Testing Setup. Jack also helped me figure out that we were using a different messageformat package than we thought, and after updating to the new @messageformat/core the test is progressing further.

Next steps:

  • incorporate the changes from React Component Testing Setup #1049
  • update my mock config to resolve testing error with messageformat
  • finish writing tests
  • see if forcing sync causes the demographic survey to save
  • investigate typings to make sure they are accurate and in typescript
  • debug the activities not saving to restore

Abby Wheelis added 9 commits October 13, 2023 10:12
setting __DEV__ to false in globals, so that it can be used throught the testing suit

calling the i18n setup in the tests should work once we incorporate the React testing changes
remove old i18n code, update types, comment out broken tests
from log statements, these answers have data and metadata, no labels
ensured that tests still pass
with the changes from e-mission#1049, we are now able to test using i18n, no need to mock!
introduced i18next for the tests

updated config in mock, and test of loading config to be accurate to what is expected (missing some '{' )

adjust formatting of function indentation
now that I understand how this function works, I got the xml (filled and unfilled) directly from console.log statements. The tests are now accurate, and cover each of the cases.
@Abby-Wheelis
Copy link
Member Author

I just learned something helpful for testing -- there were a couple of universal packages that were throwing errors in the tests. When I moused over them, they described, for example, that URL was an alias for require('url').URL. I was able to configure testing and get this to work by adding the following to my tests:

global.URL = require('url').URL;
global.Blob = require('node:buffer').Blob;

It was a quick and easy fix (which lots of testing configuration has not been) so documenting for future reference.

Abby Wheelis added 2 commits October 13, 2023 16:25
testing for saving the response, both when it works and when the  timestamps are invalid, resulting in an error
www/__mocks__/cordovaMocks.ts Outdated Show resolved Hide resolved
Comment on lines +19 to +20
global.URL = require('url').URL;
global.Blob = require('node:buffer').Blob;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm curious about this - why was it needed?
Do Node environments not have these as globals but browser environments do?

Copy link
Member Author

Choose a reason for hiding this comment

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

I remember getting an error about this, I don't remember exactly what the error was, but I found that the best way to fix it was to set it like this. comment about this. I think the issue had something to do with the testing environemnt

www/__tests__/enketoHelper.test.ts Outdated Show resolved Hide resolved
www/__tests__/enketoHelper.test.ts Outdated Show resolved Hide resolved
www/__tests__/enketoHelper.test.ts Show resolved Hide resolved
www/__mocks__/cordovaMocks.ts Outdated Show resolved Hide resolved
www/__tests__/enketoHelper.test.ts Show resolved Hide resolved
www/js/survey/enketo/enketoHelper.ts Outdated Show resolved Hide resolved
www/js/survey/enketo/enketoHelper.ts Outdated Show resolved Hide resolved
Comment on lines 94 to 110
/** @type {EnketoSurveyConfig} _config */
let _config: EnketoSurveyConfig;

/**
* _lazyLoadConfig load enketo survey config. If already loaded, return the cached config
* @returns {Promise<EnketoSurveyConfig>} enketo survey config
*/
export function _lazyLoadConfig() {
if (_config !== undefined) {
return Promise.resolve(_config);
}
return getConfig().then((newConfig) => {
logInfo('Resolved UI_CONFIG_READY promise in enketoHelper, filling in templates');
_config = newConfig.survey_info.surveys;
return _config;
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

After analyzing a bit further, I think this function is pointless.
getConfig() is already cached/lazyloaded, so we could just call it directly. The only difference is that getConfig() returns the whole config obj while this function only returns the survey_info.surveys sub-field of the config obj.

I think it would be clearer to remove this function. Anywhere it was used, call getConfig() and access the subfields from there.
Then move your EnketoSurveyConfig type def to js/types/appConfigTypes.ts (it doesn't exist yet on this branch but I started it on your other PR #1098)
The reasoning is that eventually we're going to want a single source of truth for the entire config – not scattered typings for each sub-field of the config.

Copy link
Member Author

Choose a reason for hiding this comment

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

I made those changes in d123b4f, including moving the type. I did not work the type into the existing appConfig type yet though

Abby Wheelis added 7 commits November 17, 2023 16:45
we also want to test that the timestamps follow the "minute" accuracy convention we implemented, so that if the survey and timelineEntry match within the minute, we use the timelineEntry timestamps, else use the timestamps from the survey e-mission#1063 (comment)
this form is invalide because of the start and end times mismatching
the function was not all that necessary, so I removed it

Also moved my survey config type into the appropriate place

e-mission#1063 (comment)
adding a response that should get filtered out because the version is too low e-mission#1063 (comment)
Because of the new parameter, I was able to add more test cases to resolve Labels

I needed to also clear the locally stored config out of dynamicConfig.ts
www/__mocks__/cordovaMocks.ts Outdated Show resolved Hide resolved
www/__mocks__/cordovaMocks.ts Outdated Show resolved Hide resolved
www/__tests__/clientStats.test.ts Outdated Show resolved Hide resolved
Comment on lines +13 to +16
//used test multiple configs, not used outside of test
export const resetStoredConfig = function () {
storedConfig = null;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's kind of a workaround, but ok for now

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you may want to prefix with _test_ or something similar to highlight that it is an internal function to be used for testing only

www/js/types/appConfigTypes.ts Outdated Show resolved Hide resolved
Abby-Wheelis and others added 5 commits November 19, 2023 08:33
from @JGreenlee's suggestion in review

Co-authored-by: Jack Greenlee <JackAGreenlee@gmail.com>
not all (in fact many) of the tests don't need this config at all, so the parameter should be optional to clean things up

Co-authored-by: Jack Greenlee <JackAGreenlee@gmail.com>
follow-on to making the config an optional parameter to mockBEMUserCache

Instead of passing it in EVERY TIME, it is now a fallback, and the config only needs to be specified and re-specified in the enketoHelper tests

added fallback and removed specification from tests that didn't need it
-add types for the parameters of resolveTimestamps
-update doc of this function
-cast 'timelineEntry' to trip or place where needed
-add a few fields that were missing from type defs of ConfirmedPlace and EnketoSurveyConfig
Copy link
Collaborator

@JGreenlee JGreenlee left a comment

Choose a reason for hiding this comment

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

LGTM!

JGreenlee and others added 3 commits November 21, 2023 11:55
if the survey doesn't use start and end times, timestamps will be null. This is fine and we can use optional chaining to prevent throwing an error here
Copy link

codecov bot commented Dec 3, 2023

Codecov Report

Merging #1063 (b900d1e) into service_rewrite_2023 (7121ec6) will increase coverage by 3.77%.
Report is 1 commits behind head on service_rewrite_2023.
The diff coverage is 74.68%.

Additional details and impacted files
@@                   Coverage Diff                    @@
##           service_rewrite_2023    #1063      +/-   ##
========================================================
+ Coverage                 52.67%   56.44%   +3.77%     
========================================================
  Files                        21       22       +1     
  Lines                      1198     1311     +113     
  Branches                    266      296      +30     
========================================================
+ Hits                        631      740     +109     
- Misses                      567      571       +4     
Flag Coverage Δ
unit 56.44% <74.68%> (+3.77%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
www/js/config/dynamicConfig.ts 18.32% <100.00%> (+9.79%) ⬆️
www/js/diary/timelineHelper.ts 7.31% <0.00%> (-0.10%) ⬇️
www/js/survey/enketo/enketoHelper.ts 71.81% <76.00%> (ø)

... and 2 files with indirect coverage changes

@shankari
Copy link
Contributor

shankari commented Dec 3, 2023

Oddly, "end trip force sync" seems to work, whereas just "force sync" doesn't seem to work. The only difference between the two should be that "end trip force sync" briefly starts and ends the trip before calling forceSync.

This is a long story, probably best summarized in e-mission/e-mission-docs#640
tl;dr: We only push locally cached data to the server after a trip is complete. This allows us to run the local trip detection algorithm without worrying about missing data that has been pushed up to the server. That is why you need to end the trip before force syncing to see the modified survey on the server.

Since this PR is open now and it deals with Enketo, it seems like a good opportunity to add support for client-side XML -> JSON transforms (which we've been wanting to do for awhile now!)

I noticed this in the dependencies. Exciting!
It will make it so much easier to not have to have Jack run the transformation on his personal windows laptop 😄

@Abby-Wheelis in the next round of intake form improvements, you may be able to allow participants to specify the XML file directly, either in the form (not sure if upload is supported), or by adding to the issue in a new comment or by adding to the PR directly. That should cut down significantly on the amount of busy work you have to do!

Copy link
Contributor

@shankari shankari left a comment

Choose a reason for hiding this comment

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

I am going to merge this now so I can get a release out to staging, but please fix the issues outlined here in a future fix:

  1. comments from me
  2. codecoverage results (thanks to Jiji!!) about the lines that are not covered with the current tests.

Comment on lines +13 to +16
//used test multiple configs, not used outside of test
export const resetStoredConfig = function () {
storedConfig = null;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you may want to prefix with _test_ or something similar to highlight that it is an internal function to be used for testing only

www/js/survey/enketo/enketoHelper.ts Show resolved Hide resolved
);
}

export async function fetchSurvey(url: string) {
const responseText = await fetchUrlCached(url);
Copy link
Contributor

Choose a reason for hiding this comment

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

is this the right thing to do? Is it possible for the cache to be out of date - for example, right after the user has edited the survey.

Copy link
Member Author

Choose a reason for hiding this comment

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

This function fetches the survey, not the response (ie this is what is handling the xml/json differences) that @JGreenlee added. Our previous response function [loadPreviousResponseForSurvey] does re-fetch every time.

www/js/survey/enketo/enketoHelper.ts Show resolved Hide resolved
Comment on lines +66 to +68
expect(getInstanceStr(null, opts)).toBe(null);
//if there is a prefilled survey, return it
expect(getInstanceStr(xmlModel, opts)).toBe(filled);
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't you also check with an xmlResponse that does not have the start/end datetime?

Copy link
Member Author

Choose a reason for hiding this comment

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

So, one that is already filled, but does not have the timestamps filled?

I'm a little confused what you mean, but maybe as I comb through the codecov report I can figure out where the gap you're referring to is.

@shankari shankari merged commit 9a321b7 into e-mission:service_rewrite_2023 Dec 4, 2023
4 checks passed
Abby-Wheelis pushed a commit to Abby-Wheelis/e-mission-phone that referenced this pull request Dec 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Address code reviews with "future"
Development

Successfully merging this pull request may close these issues.

3 participants