Skip to content

Commit

Permalink
Merge pull request #1086 from JGreenlee/rewrite-label-btn-services
Browse files Browse the repository at this point in the history
🏗️ Major Refactor to Remove Label Button Services
  • Loading branch information
shankari authored Nov 10, 2023
2 parents ccb50c5 + 69b18cb commit 4992e8d
Show file tree
Hide file tree
Showing 31 changed files with 771 additions and 855 deletions.
170 changes: 170 additions & 0 deletions www/__tests__/confirmHelper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { mockLogger } from '../__mocks__/globalMocks';
import * as CommHelper from '../js/commHelper';
import {
baseLabelInputDetails,
getLabelInputDetails,
getLabelOptions,
inferFinalLabels,
labelInputDetailsForTrip,
labelKeyToReadable,
labelKeyToRichMode,
labelOptionByValue,
readableLabelToKey,
verifiabilityForTrip,
} from '../js/survey/multilabel/confirmHelper';

import initializedI18next from '../js/i18nextInit';
window['i18next'] = initializedI18next;
mockLogger();

const fakeAppConfig = {
label_options: 'json/label-options.json.sample',
};
const fakeAppConfigWithModeOfStudy = {
...fakeAppConfig,
intro: {
mode_studied: 'walk',
},
};
const fakeDefaultLabelOptions = {
MODE: [
{ value: 'walk', baseMode: 'WALKING', met_equivalent: 'WALKING', kgCo2PerKm: 0 },
{ value: 'bike', baseMode: 'BICYCLING', met_equivalent: 'BICYCLING', kgCo2PerKm: 0 },
],
PURPOSE: [{ value: 'home' }, { value: 'work' }],
REPLACED_MODE: [{ value: 'no_travel' }, { value: 'walk' }, { value: 'bike' }],
translations: {
en: {
walk: 'Walk',
bike: 'Regular Bike',
no_travel: 'No travel',
home: 'Home',
work: 'To Work',
},
},
};

CommHelper.fetchUrlCached = jest
.fn()
.mockImplementation(() => JSON.stringify(fakeDefaultLabelOptions));

describe('confirmHelper', () => {
it('returns labelOptions given an appConfig', async () => {
const labelOptions = await getLabelOptions(fakeAppConfig);
expect(labelOptions).toBeTruthy();
expect(labelOptions.MODE[0].text).toEqual('Walk'); // translation is filled in
});

it('returns base labelInputDetails for a labelUserInput which does not have mode of study', () => {
const fakeLabelUserInput = {
MODE: fakeDefaultLabelOptions.MODE[1],
PURPOSE: fakeDefaultLabelOptions.PURPOSE[0],
};
const labelInputDetails = labelInputDetailsForTrip(
fakeLabelUserInput,
fakeAppConfigWithModeOfStudy,
);
expect(labelInputDetails).toEqual(baseLabelInputDetails);
});

it('returns full labelInputDetails for a labelUserInput which has the mode of study', () => {
const fakeLabelUserInput = {
MODE: fakeDefaultLabelOptions.MODE[0], // 'walk' is mode of study
PURPOSE: fakeDefaultLabelOptions.PURPOSE[0],
};
const labelInputDetails = labelInputDetailsForTrip(
fakeLabelUserInput,
fakeAppConfigWithModeOfStudy,
);
const fullLabelInputDetails = getLabelInputDetails(fakeAppConfigWithModeOfStudy);
expect(labelInputDetails).toEqual(fullLabelInputDetails);
});

it(`converts 'other' text to a label key`, () => {
const mode1 = readableLabelToKey(`Scooby Doo Mystery Machine `);
expect(mode1).toEqual('scooby_doo_mystery_machine'); // trailing space is trimmed
const mode2 = readableLabelToKey(`My niece's tricycle . `);
expect(mode2).toEqual(`my_niece's_tricycle_.`); // apostrophe and period are preserved
const purpose1 = readableLabelToKey(`Going to the store to buy 12 eggs.`);
expect(purpose1).toEqual('going_to_the_store_to_buy_12_eggs.'); // numbers are preserved
});

it(`converts keys to readable labels`, () => {
const mode1 = labelKeyToReadable(`scooby_doo_mystery_machine`);
expect(mode1).toEqual(`Scooby Doo Mystery Machine`);
const mode2 = labelKeyToReadable(`my_niece's_tricycle_.`);
expect(mode2).toEqual(`My Niece's Tricycle .`);
const purpose1 = labelKeyToReadable(`going_to_the_store_to_buy_12_eggs.`);
expect(purpose1).toEqual(`Going To The Store To Buy 12 Eggs.`);
});

it('looks up a rich mode from a label key, or humanizes the label key if there is no rich mode', () => {
const key = 'walk';
const richMode = labelKeyToRichMode(key);
expect(richMode).toEqual('Walk');
const key2 = 'scooby_doo_mystery_machine';
const readableMode = labelKeyToRichMode(key2);
expect(readableMode).toEqual('Scooby Doo Mystery Machine');
});

/* BEGIN: tests for inferences, which are loosely based on the server-side tests from
e-mission-server -> emission/tests/storageTests/TestTripQueries.py -> testExpandFinalLabels() */

it('has no final label for a trip with no user labels or inferred labels', () => {
const fakeTrip = {};
const fakeUserInput = {};
expect(inferFinalLabels(fakeTrip, fakeUserInput)).toEqual({});
expect(verifiabilityForTrip(fakeTrip, fakeUserInput)).toEqual('cannot-verify');
});

it('returns a final inference for a trip no user labels and all high-confidence inferred labels', () => {
const fakeTrip = {
inferred_labels: [{ labels: { mode_confirm: 'walk', purpose_confirm: 'exercise' }, p: 0.9 }],
};
const fakeUserInput = {};
const final = inferFinalLabels(fakeTrip, fakeUserInput);
expect(final.MODE.value).toEqual('walk');
expect(final.PURPOSE.value).toEqual('exercise');
expect(verifiabilityForTrip(fakeTrip, fakeUserInput)).toEqual('can-verify');
});

it('gives no final inference when there are user labels and no inferred labels', () => {
const fakeTrip = {};
const fakeUserInput = {
MODE: labelOptionByValue('bike', 'MODE'),
PURPOSE: labelOptionByValue('shopping', 'PURPOSE'),
};
const final = inferFinalLabels(fakeTrip, fakeUserInput);
expect(final.MODE?.value).toBeUndefined();
expect(final.PURPOSE?.value).toBeUndefined();
expect(verifiabilityForTrip(fakeTrip, fakeUserInput)).toEqual('already-verified');
});

it('still gives no final inference when there are user labels and high-confidence inferred labels', () => {
const fakeTrip = {
inferred_labels: [{ labels: { mode_confirm: 'walk', purpose_confirm: 'exercise' }, p: 0.9 }],
};
const fakeUserInput = {
MODE: labelOptionByValue('bike', 'MODE'),
PURPOSE: labelOptionByValue('shopping', 'PURPOSE'),
};
const final = inferFinalLabels(fakeTrip, fakeUserInput);
expect(final.MODE?.value).toBeUndefined();
expect(final.PURPOSE?.value).toBeUndefined();
expect(verifiabilityForTrip(fakeTrip, fakeUserInput)).toEqual('already-verified');
});

it('mixes user input labels with mixed-confidence inferred labels', () => {
const fakeTrip = {
inferred_labels: [
{ labels: { mode_confirm: 'bike', purpose_confirm: 'shopping' }, p: 0.1 },
{ labels: { mode_confirm: 'walk', purpose_confirm: 'exercise' }, p: 0.9 },
],
};
const fakeUserInput = { MODE: labelOptionByValue('bike', 'MODE') };
const final = inferFinalLabels(fakeTrip, fakeUserInput);
expect(final.MODE.value).toEqual('bike');
expect(final.PURPOSE.value).toEqual('shopping');
expect(verifiabilityForTrip(fakeTrip, fakeUserInput)).toEqual('can-verify');
});
});
33 changes: 22 additions & 11 deletions www/__tests__/diaryHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,29 @@ it('returns true/false is multi day', () => {
expect(isMultiDay('', '2023-09-18T00:00:00-09:00')).toBeFalsy();
});

//created a fake trip with relevant sections by examining log statements
/* fake trips with 'distance' in their section summaries
('count' and 'duration' are not used bygetDetectedModes) */
let myFakeTrip = {
sections: [
{ sensed_mode_str: 'BICYCLING', distance: 6013.73657416706 },
{ sensed_mode_str: 'WALKING', distance: 715.3078629361006 },
],
};
distance: 6729.0444371031606,
cleaned_section_summary: {
// count: {...}
// duration: {...}
distance: {
BICYCLING: 6013.73657416706,
WALKING: 715.3078629361006,
},
},
} as any;

let myFakeTrip2 = {
sections: [
{ sensed_mode_str: 'BICYCLING', distance: 6013.73657416706 },
{ sensed_mode_str: 'BICYCLING', distance: 715.3078629361006 },
],
...myFakeTrip,
inferred_section_summary: {
// count: {...}
// duration: {...}
distance: {
BICYCLING: 6729.0444371031606,
},
},
};

let myFakeDetectedModes = [
Expand All @@ -82,5 +93,5 @@ let myFakeDetectedModes2 = [{ mode: 'BICYCLING', icon: 'bike', color: modeColors
it('returns the detected modes, with percentages, for a trip', () => {
expect(getDetectedModes(myFakeTrip)).toEqual(myFakeDetectedModes);
expect(getDetectedModes(myFakeTrip2)).toEqual(myFakeDetectedModes2);
expect(getDetectedModes({})).toEqual([]); // empty trip, no sections, no modes
expect(getDetectedModes({} as any)).toEqual([]); // empty trip, no sections, no modes
});
51 changes: 34 additions & 17 deletions www/__tests__/inputMatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import {
validUserInputForDraftTrip,
validUserInputForTimelineEntry,
getNotDeletedCandidates,
getUserInputForTrip,
getUserInputForTimelineEntry,
getAdditionsForTimelineEntry,
getUniqueEntries,
} from '../js/survey/inputMatcher';
import { TlEntry, UserInput } from '../js/types/diaryTypes';
import { CompositeTrip, TimelineEntry, UserInputEntry } from '../js/types/diaryTypes';

describe('input-matcher', () => {
let userTrip: UserInput;
let trip: TlEntry;
let userTrip: UserInputEntry;
let trip: TimelineEntry;
let nextTrip: TimelineEntry;

beforeEach(() => {
/*
Expand Down Expand Up @@ -46,9 +47,16 @@ describe('input-matcher', () => {
enter_ts: 1437605000,
exit_ts: 1437605000,
duration: 100,
getNextEntry: jest.fn(),
};

nextTrip = {
key: 'BAR',
origin_key: 'BAR',
start_ts: 1437606000,
end_ts: 1437607000,
enter_ts: 1437607000,
exit_ts: 1437607000,
duration: 100,
};
// mock Logger
window['Logger'] = { log: console.log };
});
Expand Down Expand Up @@ -78,7 +86,7 @@ describe('input-matcher', () => {
const validTrp = {
end_ts: 1437604764,
start_ts: 1437601247,
};
} as CompositeTrip;
const validUserInput = validUserInputForDraftTrip(validTrp, userTrip, false);
expect(validUserInput).toBeTruthy();
});
Expand All @@ -87,7 +95,7 @@ describe('input-matcher', () => {
const invalidTrip = {
end_ts: 0,
start_ts: 0,
};
} as CompositeTrip;
const invalidUserInput = validUserInputForDraftTrip(invalidTrip, userTrip, false);
expect(invalidUserInput).toBeFalsy();
});
Expand All @@ -96,28 +104,37 @@ describe('input-matcher', () => {
// we need valid key and origin_key for validUserInputForTimelineEntry test
trip['key'] = 'analysis/confirmed_place';
trip['origin_key'] = 'analysis/confirmed_place';
const validTimelineEntry = validUserInputForTimelineEntry(trip, userTrip, false);
const validTimelineEntry = validUserInputForTimelineEntry(trip, nextTrip, userTrip, false);
expect(validTimelineEntry).toBeTruthy();
});

it('tests validUserInputForTimelineEntry with tlEntry with invalid key and origin_key', () => {
const invalidTlEntry = trip;
const invalidTimelineEntry = validUserInputForTimelineEntry(invalidTlEntry, userTrip, false);
const invalidTimelineEntry = validUserInputForTimelineEntry(
invalidTlEntry,
null,
userTrip,
false,
);
expect(invalidTimelineEntry).toBeFalsy();
});

it('tests validUserInputForTimelineEntry with tlEntry with invalie start & end time', () => {
const invalidTlEntry: TlEntry = {
const invalidTlEntry: TimelineEntry = {
key: 'analysis/confirmed_place',
origin_key: 'analysis/confirmed_place',
start_ts: 1,
end_ts: 1,
enter_ts: 1,
exit_ts: 1,
duration: 1,
getNextEntry: jest.fn(),
};
const invalidTimelineEntry = validUserInputForTimelineEntry(invalidTlEntry, userTrip, false);
const invalidTimelineEntry = validUserInputForTimelineEntry(
invalidTlEntry,
null,
userTrip,
false,
);
expect(invalidTimelineEntry).toBeFalsy();
});

Expand Down Expand Up @@ -210,13 +227,13 @@ describe('input-matcher', () => {

// make the linst unsorted and then check if userInputWriteThird(latest one) is return output
const userInputList = [userInputWriteSecond, userInputWriteThird, userInputWriteFirst];
const mostRecentEntry = getUserInputForTrip(trip, {}, userInputList);
const mostRecentEntry = getUserInputForTimelineEntry(trip, nextTrip, userInputList);
expect(mostRecentEntry).toMatchObject(userInputWriteThird);
});

it('tests getUserInputForTrip with invalid userInputList', () => {
const userInputList = undefined;
const mostRecentEntry = getUserInputForTrip(trip, {}, userInputList);
const mostRecentEntry = getUserInputForTimelineEntry(trip, nextTrip, userInputList);
expect(mostRecentEntry).toBe(undefined);
});

Expand All @@ -226,13 +243,13 @@ describe('input-matcher', () => {
trip['origin_key'] = 'analysis/confirmed_place';

// check if the result keep the all valid userTrip items
const matchingAdditions = getAdditionsForTimelineEntry(trip, additionsList);
const matchingAdditions = getAdditionsForTimelineEntry(trip, nextTrip, additionsList);
expect(matchingAdditions).toHaveLength(5);
});

it('tests getAdditionsForTimelineEntry with invalid additionsList', () => {
const additionsList = undefined;
const matchingAdditions = getAdditionsForTimelineEntry(trip, additionsList);
const matchingAdditions = getAdditionsForTimelineEntry(trip, nextTrip, additionsList);
expect(matchingAdditions).toMatchObject([]);
});

Expand Down
1 change: 1 addition & 0 deletions www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@
"registration-check-token": "User registration error. Please check your token and try again.",
"not-registered-cant-contact": "User is not registered, so the server cannot be contacted.",
"while-initializing-label": "While initializing Label tab: ",
"while-loading-pipeline-range": "Error while loading pipeline range",
"while-populating-composite": "Error while populating composite trips",
"while-loading-another-week": "Error while loading travel of {{when}} week",
"while-loading-specific-week": "Error while loading travel for the week of {{day}}",
Expand Down
3 changes: 0 additions & 3 deletions www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import './js/controllers.js';
import './js/services.js';
import './js/i18n-utils.js';
import './js/main.js';
import './js/survey/multilabel/multi-label-ui.js';
import './js/diary.js';
import './js/diary/services.js';
import './js/survey/enketo/answer.js';
import './js/survey/enketo/enketo-trip-button.js';
import './js/survey/enketo/enketo-add-note-button.js';
import './js/control/emailService.js';
import './js/metrics-factory.js';
import './js/metrics-mappings.js';
Expand Down
4 changes: 1 addition & 3 deletions www/js/diary.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import LabelTab from './diary/LabelTab';
angular
.module('emission.main.diary', [
'emission.main.diary.services',
'emission.survey.multilabel.buttons',
'emission.survey.enketo.add-note-button',
'emission.survey.enketo.trip.button',
'emission.plugin.logger',
'emission.survey.enketo.answer',
])

.config(function ($stateProvider) {
Expand Down
Loading

0 comments on commit 4992e8d

Please sign in to comment.