diff --git a/app/integration_test/fixtures/drugs/ibuprofen_with_proper_guideline.dart b/app/integration_test/fixtures/drugs/ibuprofen_with_proper_guideline.dart index 074c3795..bd6e4b16 100644 --- a/app/integration_test/fixtures/drugs/ibuprofen_with_proper_guideline.dart +++ b/app/integration_test/fixtures/drugs/ibuprofen_with_proper_guideline.dart @@ -1,6 +1,6 @@ import 'package:app/common/models/drug/drug.dart'; -import 'package:app/common/models/drug/guideline.dart'; -import 'package:app/common/models/drug/warning_level.dart'; + +import '../guidelines/ibuprofen_cyp2c9_normal.dart'; final ibuprofenWithProperGuideline = Drug( id: '6407768b92a4868065b6c466', @@ -19,26 +19,6 @@ final ibuprofenWithProperGuideline = Drug( 'Vicoprofen', ]), guidelines: [ - Guideline( - id: '64552859a1b68082babc8c31', - version: 1, - lookupkey: { - 'CYP2C9': ['2.0'] - }, - externalData: [ - GuidelineExtData( - source: 'CPIC', - guidelineName: 'CYP2C9 and NSAIDs', - guidelineUrl: 'https://cpicpgx.org/guidelines/cpic-guideline-for-nsaids-based-on-cyp2c9-genotype/', - implications: {'CYP2C9': 'Normal metabolism'}, - recommendation: 'Initiate therapy with recommended starting dose. In accordance with the prescribing information, use the lowest effective dosage for shortest duration consistent with individual patient treatment goals.', - comments: 'n/a') - ], - annotations: GuidelineAnnotations( - implication: 'You break down ibuprofen as expected.', - recommendation: 'You can use ibuprofen at standard dose. Consult your pharmacist or doctor for more information.', - warningLevel: WarningLevel.green, - ), - ), + ibuprofenCyp2c9NormalGuideline, ], ); diff --git a/app/integration_test/fixtures/drugs/oxcarbazepine_with_hlab1502_guideline.dart b/app/integration_test/fixtures/drugs/oxcarbazepine_with_hlab1502_guideline.dart new file mode 100644 index 00000000..8ba605b2 --- /dev/null +++ b/app/integration_test/fixtures/drugs/oxcarbazepine_with_hlab1502_guideline.dart @@ -0,0 +1,41 @@ +import 'package:app/common/models/drug/drug.dart'; +import 'package:app/common/models/drug/guideline.dart'; +import 'package:app/common/models/drug/warning_level.dart'; + +final oxcarbazepineWithHlab1502Guideline = Drug( + id: '6407768c92a4868065b6ceb6', + version: 1, + name: 'oxcarbazepine', + rxNorm: 'RxNorm:32624', + annotations: DrugAnnotations( + drugclass: 'Anti-seizure', + indication: 'Oxcarbazepine is an anti-epileptic used to prevent seizures.', + brandNames: ['Oxtellar', 'Trileptal'], + ), + guidelines: [ + Guideline( + id: '64552859a1b68082babc8dc6', + version: 1, + lookupkey: { + 'HLA-B': ['*15:02 negative'], + }, + externalData: [ + GuidelineExtData( + source: 'CPIC', + guidelineName: 'HLA-A, HLA-B and Carbamazepine and Oxcarbazepine', + guidelineUrl: 'https://cpicpgx.org/guidelines/guideline-for-carbamazepine-and-hla-b/', + implications: { + 'HLA-B': 'Normal risk of oxcarbazepine-induced SJS/TEN', + }, + recommendation: 'Use oxcarbazepine per standard dosing guidelines.', + comments: 'n/a', + ), + ], + annotations: GuidelineAnnotations( + implication: 'You have a normal risk for side effects.', + recommendation: 'You can use oxcarbazepine at standard dose. Consult your pharmacist or doctor for more information.', + warningLevel: WarningLevel.green, + ), + ), + ], +); \ No newline at end of file diff --git a/app/integration_test/fixtures/drugs/pazopanib_with_multiple_any_not_handled_fallback_guidelines.dart b/app/integration_test/fixtures/drugs/pazopanib_with_multiple_any_not_handled_fallback_guidelines.dart index e198fc82..24298150 100644 --- a/app/integration_test/fixtures/drugs/pazopanib_with_multiple_any_not_handled_fallback_guidelines.dart +++ b/app/integration_test/fixtures/drugs/pazopanib_with_multiple_any_not_handled_fallback_guidelines.dart @@ -2,7 +2,7 @@ import 'package:app/common/models/drug/drug.dart'; import 'package:app/common/models/drug/guideline.dart'; import 'package:app/common/models/drug/warning_level.dart'; -import '../guidelines/pazopanib_hlab5701_positive_ugt1a1_poor_guideline.dart'; +import '../guidelines/pazopanib_hlab5701_positive_ugt1a1_poor.dart'; final pazopanibWithMultipleAnyNotHandledFallbackGuidelines = Drug( id: '6686a865826414ec5b05c44e', diff --git a/app/integration_test/fixtures/guidelines/ibuprofen_cyp2c9_normal.dart b/app/integration_test/fixtures/guidelines/ibuprofen_cyp2c9_normal.dart new file mode 100644 index 00000000..a4ccff23 --- /dev/null +++ b/app/integration_test/fixtures/guidelines/ibuprofen_cyp2c9_normal.dart @@ -0,0 +1,24 @@ +import 'package:app/common/models/drug/guideline.dart'; +import 'package:app/common/models/drug/warning_level.dart'; + +final ibuprofenCyp2c9NormalGuideline = Guideline( + id: '64552859a1b68082babc8c31', + version: 1, + lookupkey: { + 'CYP2C9': ['2.0'] + }, + externalData: [ + GuidelineExtData( + source: 'CPIC', + guidelineName: 'CYP2C9 and NSAIDs', + guidelineUrl: 'https://cpicpgx.org/guidelines/cpic-guideline-for-nsaids-based-on-cyp2c9-genotype/', + implications: {'CYP2C9': 'Normal metabolism'}, + recommendation: 'Initiate therapy with recommended starting dose. In accordance with the prescribing information, use the lowest effective dosage for shortest duration consistent with individual patient treatment goals.', + comments: 'n/a') + ], + annotations: GuidelineAnnotations( + implication: 'You break down ibuprofen as expected.', + recommendation: 'You can use ibuprofen at standard dose. Consult your pharmacist or doctor for more information.', + warningLevel: WarningLevel.green, + ), +); \ No newline at end of file diff --git a/app/integration_test/fixtures/guidelines/pazopanib_hlab5701_positive_ugt1a1_poor_guideline.dart b/app/integration_test/fixtures/guidelines/pazopanib_hlab5701_positive_ugt1a1_poor.dart similarity index 100% rename from app/integration_test/fixtures/guidelines/pazopanib_hlab5701_positive_ugt1a1_poor_guideline.dart rename to app/integration_test/fixtures/guidelines/pazopanib_hlab5701_positive_ugt1a1_poor.dart diff --git a/app/integration_test/fixtures/set_app_data.dart b/app/integration_test/fixtures/set_app_data.dart index 3b6b1abf..11d23106 100644 --- a/app/integration_test/fixtures/set_app_data.dart +++ b/app/integration_test/fixtures/set_app_data.dart @@ -21,6 +21,7 @@ void setUserDataForGuideline( Guideline guideline, { List? explicitNoResult, Map? explicitLookups, + bool missingLookup = false, }) { UserData.instance.labData = UserData.instance.labData ?? []; for (final gene in guideline.lookupkey.keys) { @@ -49,9 +50,9 @@ void setUserDataForGuideline( // multiple HLA-B variants in the tests or overwrite a specific HLA-B // variant, we will need to check for the genotype key (which is in the // current setup not possible without the proper variant) - final lookupIsMissing = lookupkey == SpecialLookup.noResult.value || + final resultIsMissing = lookupkey == SpecialLookup.noResult.value || (explicitNoResult?.contains(gene) ?? false); - if (!lookupIsMissing) { + if (!resultIsMissing) { UserData.instance.labData = UserData.instance.labData!.filter( (labResult) => labResult.gene != gene ).toList(); @@ -68,7 +69,7 @@ void setUserDataForGuideline( phenotype: userDataConfig.phenotype, variant: userDataConfig.variant, allelesTested: userDataConfig.variant, - lookupkey: userDataConfig.lookupkey, + lookupkey: missingLookup ? null : userDataConfig.lookupkey, )); } } @@ -85,15 +86,23 @@ void addDrugToDrugsWithGuidelines(Drug drug) { void setAppData({ required Drug drug, - required Guideline guideline, + Guideline? guideline, List? explicitNoResult, Map? explicitLookups, + bool missingLookup = false, }) { addDrugToDrugsWithGuidelines(drug); - initializeGenotypeResultKeys().values.forEach(setGenotypeResult); - setUserDataForGuideline( - guideline, - explicitNoResult: explicitNoResult, - explicitLookups: explicitLookups, - ); + for (final noResultGenotypeResult in initializeGenotypeResultKeys().values) { + if (drug.guidelineGenotypes.contains(noResultGenotypeResult.key.value)) { + setGenotypeResult(noResultGenotypeResult); + } + } + if (guideline != null) { + setUserDataForGuideline( + guideline, + explicitNoResult: explicitNoResult, + explicitLookups: explicitLookups, + missingLookup: missingLookup, + ); + } } diff --git a/app/integration_test/report_test.dart b/app/integration_test/report_test.dart index 2d34c41a..de202a81 100644 --- a/app/integration_test/report_test.dart +++ b/app/integration_test/report_test.dart @@ -5,8 +5,13 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:provider/provider.dart'; +import 'fixtures/drugs/aripiprazole_with_any_not_handled_guideline.dart'; +import 'fixtures/drugs/ibuprofen_with_proper_guideline.dart'; +import 'fixtures/drugs/oxcarbazepine_with_hlab1502_guideline.dart'; +import 'fixtures/drugs/pazopanib_with_multiple_any_not_handled_fallback_guidelines.dart'; import 'fixtures/guidelines/aripiprazole_cyp2d6_poor.dart'; -import 'fixtures/guidelines/pazopanib_hlab5701_positive_ugt1a1_poor_guideline.dart'; +import 'fixtures/guidelines/ibuprofen_cyp2c9_normal.dart'; +import 'fixtures/guidelines/pazopanib_hlab5701_positive_ugt1a1_poor.dart'; import 'fixtures/set_app_data.dart'; void main() { @@ -42,19 +47,50 @@ void main() { ); } + Finder findGeneCard(String gene) { + return find.ancestor( + of: find.textContaining(gene, skipOffstage: false), + matching: find.byType(GeneCard, skipOffstage: false), + ); + } + Future testReportContent( WidgetTester tester, { - required List expectedGuidelines, - required List missingResults, + required Map testData, + Map? missingLookupData, }) async { - expectedGuidelines.forEach(setUserDataForGuideline); - missingResults.forEach(setGenotypeResult); + for (final drug in testData.keys) { + setAppData(drug: drug, guideline: testData[drug]); + } + if (missingLookupData != null) { + for (final drug in missingLookupData.keys) { + setAppData( + drug: drug, + guideline: missingLookupData[drug], + missingLookup: true, + ); + } + } await loadReportPage(tester); - final expectedGenes = [ - ...expectedGuidelines.flatMap( + final expectedGenesWithGuidelines = testData.values.filterNotNull().flatMap( + (guideline) => guideline.lookupkey.keys + ); + final expectedGenesWithUnmappableGuideline = + missingLookupData?.values.flatMap( (guideline) => guideline.lookupkey.keys - ), - ...missingResults.map((genotypeResult) => genotypeResult.gene), + ) ?? []; + final expectedNotTestedGenes = testData.keys.filter( + (drug) => testData[drug] == null + ).map( + (drug) => UserData.instance.genotypeResults![GenotypeKey( + drug.guidelines.first.lookupkey.keys.first, + drug.guidelines.first.lookupkey.values.first.first, + ).value]?.gene + ).filterNotNull(); + final expectedGenes = [ + ...expectedGenesWithGuidelines, + ...expectedGenesWithUnmappableGuideline, + ...expectedNotTestedGenes, ]; expect( find.byType(GeneCard, skipOffstage: false), @@ -68,23 +104,68 @@ void main() { findsNWidgets(expectedGeneOccurrences), ); } + if (missingLookupData != null) { + for (final guideline in missingLookupData.values) { + for (final lookup in guideline.lookupkey.entries) { + final gene = lookup.key; + final testPhenotype = lookup.value.first; + final geneCard = findGeneCard(gene); + expect( + find.descendant( + of: geneCard, + matching: find.text(testPhenotype, skipOffstage: false) + ), + findsOneWidget, + ); + } + } + } + final context = tester.element(find.byType(Scaffold).first); + if (expectedNotTestedGenes.isNotEmpty) { + expect( + find.text(context.l10n.report_no_result_genes, skipOffstage: false), + findsOneWidget, + ); + for (final gene in expectedNotTestedGenes) { + final geneCardsFinder = findGeneCard(gene); + expect( + find.descendant( + of: geneCardsFinder, + matching: + find.text(context.l10n.general_not_tested, skipOffstage: false), + skipOffstage: false, + ), + findsOneWidget, + ); + final geneCard = tester.widgetList(geneCardsFinder).last as GeneCard; + expect(geneCard.color, PharMeTheme.onSurfaceColor); + } + } else { + expect( + find.text(context.l10n.report_no_result_genes, skipOffstage: false), + findsNothing, + ); + } } group('integration test for the report page', () { testWidgets( 'tests that genes for drugs with guidelines are shown', (tester) async { - final expectedGuidelines = [ - aripiprazoleCyp2d6PoorGuideline, - pazopanibHlab5701PositiveUgt1a1PoorGuideline, - ]; - final missingResults = [ - GenotypeResult.missingResult('HLA-B', variant: '*15:02'), - ]; + final testData = { + aripiprazoleWithAnyNotHandledFallbackGuideline: + aripiprazoleCyp2d6PoorGuideline, + pazopanibWithMultipleAnyNotHandledFallbackGuidelines: + pazopanibHlab5701PositiveUgt1a1PoorGuideline, + oxcarbazepineWithHlab1502Guideline: null, + }; + final missingLookupData = { + ibuprofenWithProperGuideline: ibuprofenCyp2c9NormalGuideline, + }; await testReportContent( tester, - expectedGuidelines: expectedGuidelines, - missingResults: missingResults, + testData: testData, + missingLookupData: missingLookupData, ); }, ); diff --git a/app/lib/report/pages/report.dart b/app/lib/report/pages/report.dart index 94ae3be4..b70739b4 100644 --- a/app/lib/report/pages/report.dart +++ b/app/lib/report/pages/report.dart @@ -121,6 +121,11 @@ class GeneCard extends StatelessWidget { final GenotypeResult genotypeResult; final WarningLevelCounts warningLevelCounts; + @visibleForTesting + Color? get color => _hasNoResult(genotypeResult) + ? PharMeTheme.onSurfaceColor + : _getHighestSeverityColor(warningLevelCounts); + Color? _getHighestSeverityColor(WarningLevelCounts warningLevelCounts) { final sortedWarningLevels = WarningLevel.values.sortedByDescending( (warningLevel) => warningLevel.severity @@ -138,7 +143,7 @@ class GeneCard extends StatelessWidget { GeneRoute(genotypeResult: genotypeResult) ), radius: 16, - color: _hasNoResult(genotypeResult) ? PharMeTheme.onSurfaceColor : _getHighestSeverityColor(warningLevelCounts), + color: color, child: IntrinsicHeight(child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/pharme.code-workspace b/pharme.code-workspace index 290bb5fe..15b719f0 100644 --- a/pharme.code-workspace +++ b/pharme.code-workspace @@ -41,6 +41,7 @@ "Backupper", "brandnames", "bupropion", + "Carbamazepine", "cinacalcet", "citalopram", "clopidogrel", @@ -89,6 +90,8 @@ "noto", "NSAID", "omeprazole", + "oxcarbazepine", + "Oxtellar", "pantoprazole", "paroxetine", "pazopanib", @@ -118,6 +121,8 @@ "tacrolimus", "terbinafine", "tramadol", + "Trileptal", + "Unmappable", "unscrollable", "unstage", "unstaged",