Skip to content

Commit 4d28c04

Browse files
authored
fix(predict): chart only display active (MetaMask#22677)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** - Refactors the details screen so that it only passed active outcomes to the chart (or hides it) <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: NA ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/22900f2b-988e-496a-a554-59562d2caafc <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Restricts the market details chart to open outcomes (max 3), updates price history inputs and render conditions, and adds/updates tests to cover these cases. > > - **PredictMarketDetails** (`PredictMarketDetails.tsx`) > - Prepare chart data from only open outcomes via `chartOpenOutcomes` (capped at 3) and derive `chartOutcomeTokenIds`. > - Use `chartOutcomeTokenIds` for `usePredictPriceHistory` and enable only when non-empty. > - Build `chartData` labels/colors from `chartOpenOutcomes` and render chart only when `chartOpenOutcomes.length > 0` (instead of checking partial resolution state). > - Keep real-time price updates for open outcomes; no functional changes elsewhere. > - **Tests** (`PredictMarketDetails.test.tsx`) > - Add/adjust cases to verify: no chart when no open outcomes; chart when at least one open; closed outcomes filtered; limit to first 3 open outcomes. > - Update mocks/fixtures to include `status` fields; add `usePredictMeasurement` mock; minor expectations aligned with new outcomes tab and chart behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6974586. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent ee0a135 commit 4d28c04

2 files changed

Lines changed: 269 additions & 17 deletions

File tree

app/components/UI/Predict/views/PredictMarketDetails/PredictMarketDetails.test.tsx

Lines changed: 242 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ jest.mock('../../hooks/usePredictPrices', () => ({
228228
})),
229229
}));
230230

231+
jest.mock('../../hooks/usePredictMeasurement', () => ({
232+
usePredictMeasurement: jest.fn(),
233+
}));
234+
231235
jest.mock('../../components/PredictDetailsChart/PredictDetailsChart', () => {
232236
const { View, Text } = jest.requireActual('react-native');
233237
return function MockPredictDetailsChart() {
@@ -425,12 +429,14 @@ function createMockMarket(overrides = {}) {
425429
image: 'https://example.com/bitcoin.png',
426430
endDate: '2024-12-31T23:59:59Z',
427431
providerId: 'polymarket',
432+
status: 'open',
428433
tags: [],
429434
outcomes: [
430435
{
431436
id: 'outcome-1',
432437
title: 'Yes',
433438
groupItemTitle: 'Yes',
439+
status: 'open',
434440
tokens: [
435441
{
436442
id: 'token-1',
@@ -836,11 +842,16 @@ describe('PredictMarketDetails', () => {
836842
describe('Chart Rendering', () => {
837843
it('renders single outcome chart for single outcome markets', () => {
838844
const singleOutcomeMarket = createMockMarket({
845+
status: 'open',
839846
outcomes: [
840847
{
841848
id: 'outcome-1',
842849
title: 'Yes',
843-
tokens: [{ id: 'token-1', price: 0.65 }],
850+
status: 'open',
851+
tokens: [
852+
{ id: 'token-1', title: 'Yes', price: 0.65 },
853+
{ id: 'token-2', title: 'No', price: 0.35 },
854+
],
844855
volume: 1000000,
845856
},
846857
],
@@ -863,18 +874,21 @@ describe('PredictMarketDetails', () => {
863874
{
864875
id: 'outcome-1',
865876
title: 'Option A',
877+
status: 'open',
866878
tokens: [{ id: 'token-1', price: 0.4 }],
867879
volume: 1000000,
868880
},
869881
{
870882
id: 'outcome-2',
871883
title: 'Option B',
884+
status: 'open',
872885
tokens: [{ id: 'token-2', price: 0.3 }],
873886
volume: 500000,
874887
},
875888
{
876889
id: 'outcome-3',
877890
title: 'Option C',
891+
status: 'open',
878892
tokens: [{ id: 'token-3', price: 0.3 }],
879893
volume: 300000,
880894
},
@@ -885,6 +899,215 @@ describe('PredictMarketDetails', () => {
885899

886900
expect(screen.getByTestId('predict-details-chart')).toBeOnTheScreen();
887901
});
902+
903+
it('does not render chart when all outcomes are closed', () => {
904+
const closedOutcomesMarket = createMockMarket({
905+
status: 'closed',
906+
outcomes: [
907+
{
908+
id: 'outcome-1',
909+
title: 'Yes',
910+
status: 'closed',
911+
tokens: [
912+
{ id: 'token-1', price: 1.0, title: 'Yes' },
913+
{ id: 'token-1b', price: 0.0, title: 'No' },
914+
],
915+
volume: 1000000,
916+
},
917+
{
918+
id: 'outcome-2',
919+
title: 'No',
920+
status: 'closed',
921+
tokens: [
922+
{ id: 'token-2', price: 0.0, title: 'Yes' },
923+
{ id: 'token-2b', price: 1.0, title: 'No' },
924+
],
925+
volume: 500000,
926+
},
927+
],
928+
});
929+
930+
setupPredictMarketDetailsTest(closedOutcomesMarket);
931+
932+
expect(
933+
screen.queryByTestId('predict-details-chart'),
934+
).not.toBeOnTheScreen();
935+
});
936+
937+
it('does not render chart when market has no open outcomes', () => {
938+
const noOpenOutcomesMarket = createMockMarket({
939+
outcomes: [
940+
{
941+
id: 'outcome-1',
942+
title: 'Option A',
943+
status: 'closed',
944+
tokens: [{ id: 'token-1', price: 0.5 }],
945+
volume: 1000000,
946+
},
947+
],
948+
});
949+
950+
setupPredictMarketDetailsTest(noOpenOutcomesMarket);
951+
952+
expect(
953+
screen.queryByTestId('predict-details-chart'),
954+
).not.toBeOnTheScreen();
955+
});
956+
957+
it('renders chart when market has at least one open outcome', () => {
958+
const mixedStatusMarket = createMockMarket({
959+
outcomes: [
960+
{
961+
id: 'outcome-1',
962+
title: 'Option A',
963+
status: 'closed',
964+
tokens: [{ id: 'token-1', price: 1.0 }],
965+
volume: 1000000,
966+
},
967+
{
968+
id: 'outcome-2',
969+
title: 'Option B',
970+
status: 'open',
971+
tokens: [{ id: 'token-2', price: 0.5 }],
972+
volume: 500000,
973+
},
974+
],
975+
});
976+
977+
setupPredictMarketDetailsTest(mixedStatusMarket);
978+
979+
expect(screen.getByTestId('predict-details-chart')).toBeOnTheScreen();
980+
});
981+
982+
it('limits chart data to first 3 open outcomes when more are available', () => {
983+
const { usePredictPriceHistory } = jest.requireMock(
984+
'../../hooks/usePredictPriceHistory',
985+
);
986+
987+
const marketWithManyOutcomes = createMockMarket({
988+
outcomes: [
989+
{
990+
id: 'outcome-1',
991+
title: 'Option A',
992+
status: 'open',
993+
tokens: [{ id: 'token-1', price: 0.25 }],
994+
volume: 1000000,
995+
},
996+
{
997+
id: 'outcome-2',
998+
title: 'Option B',
999+
status: 'open',
1000+
tokens: [{ id: 'token-2', price: 0.25 }],
1001+
volume: 800000,
1002+
},
1003+
{
1004+
id: 'outcome-3',
1005+
title: 'Option C',
1006+
status: 'open',
1007+
tokens: [{ id: 'token-3', price: 0.25 }],
1008+
volume: 600000,
1009+
},
1010+
{
1011+
id: 'outcome-4',
1012+
title: 'Option D',
1013+
status: 'open',
1014+
tokens: [{ id: 'token-4', price: 0.25 }],
1015+
volume: 400000,
1016+
},
1017+
],
1018+
});
1019+
1020+
setupPredictMarketDetailsTest(marketWithManyOutcomes);
1021+
1022+
expect(usePredictPriceHistory).toHaveBeenCalledWith(
1023+
expect.objectContaining({
1024+
marketIds: ['token-1', 'token-2', 'token-3'],
1025+
}),
1026+
);
1027+
});
1028+
1029+
it('filters out closed outcomes from chart data', () => {
1030+
const { usePredictPriceHistory } = jest.requireMock(
1031+
'../../hooks/usePredictPriceHistory',
1032+
);
1033+
1034+
const marketWithMixedStatus = createMockMarket({
1035+
outcomes: [
1036+
{
1037+
id: 'outcome-1',
1038+
title: 'Option A',
1039+
status: 'closed',
1040+
tokens: [{ id: 'token-1', price: 1.0 }],
1041+
volume: 1000000,
1042+
},
1043+
{
1044+
id: 'outcome-2',
1045+
title: 'Option B',
1046+
status: 'open',
1047+
tokens: [{ id: 'token-2', price: 0.6 }],
1048+
volume: 800000,
1049+
},
1050+
{
1051+
id: 'outcome-3',
1052+
title: 'Option C',
1053+
status: 'open',
1054+
tokens: [{ id: 'token-3', price: 0.4 }],
1055+
volume: 600000,
1056+
},
1057+
],
1058+
});
1059+
1060+
setupPredictMarketDetailsTest(marketWithMixedStatus);
1061+
1062+
expect(usePredictPriceHistory).toHaveBeenCalledWith(
1063+
expect.objectContaining({
1064+
marketIds: ['token-2', 'token-3'],
1065+
}),
1066+
);
1067+
expect(screen.getByTestId('predict-details-chart')).toBeOnTheScreen();
1068+
});
1069+
1070+
it('does not render chart when market has no open outcomes', () => {
1071+
const emptyOutcomesMarket = createMockMarket({
1072+
status: 'closed',
1073+
outcomes: [
1074+
{
1075+
id: 'outcome-1',
1076+
title: 'Placeholder',
1077+
status: 'closed',
1078+
tokens: [
1079+
{ id: 'token-1', title: 'Token', price: 0.5 },
1080+
{ id: 'token-2', title: 'Token 2', price: 0.5 },
1081+
],
1082+
volume: 0,
1083+
},
1084+
],
1085+
});
1086+
1087+
setupPredictMarketDetailsTest(emptyOutcomesMarket);
1088+
1089+
expect(
1090+
screen.queryByTestId('predict-details-chart'),
1091+
).not.toBeOnTheScreen();
1092+
});
1093+
1094+
it('renders chart even when outcomes have no tokens', () => {
1095+
const noTokensMarket = createMockMarket({
1096+
outcomes: [
1097+
{
1098+
id: 'outcome-1',
1099+
title: 'Option A',
1100+
status: 'open',
1101+
tokens: [],
1102+
volume: 1000000,
1103+
},
1104+
],
1105+
});
1106+
1107+
setupPredictMarketDetailsTest(noTokensMarket);
1108+
1109+
expect(screen.getByTestId('predict-details-chart')).toBeOnTheScreen();
1110+
});
8881111
});
8891112

8901113
describe('Tab Navigation', () => {
@@ -1340,22 +1563,26 @@ describe('PredictMarketDetails', () => {
13401563

13411564
it('renders outcomes tab for multi-outcome markets', () => {
13421565
const multiOutcomeMarket = createMockMarket({
1566+
status: 'open',
13431567
outcomes: [
13441568
{
13451569
id: 'outcome-1',
13461570
title: 'Option A',
1571+
status: 'open',
13471572
tokens: [{ id: 'token-1', price: 0.4 }],
13481573
volume: 1000000,
13491574
},
13501575
{
13511576
id: 'outcome-2',
13521577
title: 'Option B',
1578+
status: 'open',
13531579
tokens: [{ id: 'token-2', price: 0.3 }],
13541580
volume: 500000,
13551581
},
13561582
{
13571583
id: 'outcome-3',
13581584
title: 'Option C',
1585+
status: 'open',
13591586
tokens: [{ id: 'token-3', price: 0.3 }],
13601587
volume: 300000,
13611588
},
@@ -1365,7 +1592,9 @@ describe('PredictMarketDetails', () => {
13651592
setupPredictMarketDetailsTest(multiOutcomeMarket);
13661593

13671594
// Outcomes is the default tab when there are no positions
1368-
expect(screen.getAllByTestId('predict-market-outcome')).toHaveLength(3);
1595+
expect(
1596+
screen.getByTestId('predict-market-details-outcomes-tab'),
1597+
).toBeOnTheScreen();
13691598
});
13701599

13711600
it('does not render outcomes tab for single outcome markets', () => {
@@ -1909,11 +2138,16 @@ describe('PredictMarketDetails', () => {
19092138

19102139
it('handles chart color selection for single outcome', () => {
19112140
const singleOutcomeMarket = createMockMarket({
2141+
status: 'open',
19122142
outcomes: [
19132143
{
19142144
id: 'outcome-1',
19152145
title: 'Yes',
1916-
tokens: [{ id: 'token-1', price: 0.65 }],
2146+
status: 'open',
2147+
tokens: [
2148+
{ id: 'token-1', title: 'Yes', price: 0.65 },
2149+
{ id: 'token-2', title: 'No', price: 0.35 },
2150+
],
19172151
volume: 1000000,
19182152
},
19192153
],
@@ -1927,16 +2161,19 @@ describe('PredictMarketDetails', () => {
19272161

19282162
it('handles chart color selection for multiple outcomes', () => {
19292163
const multiOutcomeMarket = createMockMarket({
2164+
status: 'open',
19302165
outcomes: [
19312166
{
19322167
id: 'outcome-1',
19332168
title: 'Option A',
2169+
status: 'open',
19342170
tokens: [{ id: 'token-1', price: 0.4 }],
19352171
volume: 1000000,
19362172
},
19372173
{
19382174
id: 'outcome-2',
19392175
title: 'Option B',
2176+
status: 'open',
19402177
tokens: [{ id: 'token-2', price: 0.3 }],
19412178
volume: 500000,
19422179
},
@@ -2360,7 +2597,7 @@ describe('PredictMarketDetails', () => {
23602597
expect(screen.getByText('predict.resolved_outcomes')).toBeOnTheScreen();
23612598
});
23622599

2363-
it('hides chart when multipleOpenOutcomesPartiallyResolved is true', () => {
2600+
it('renders chart when market has open outcomes despite partial resolution', () => {
23642601
const marketWithPartialResolution = createMockMarket({
23652602
status: 'open',
23662603
outcomes: [
@@ -2389,9 +2626,7 @@ describe('PredictMarketDetails', () => {
23892626

23902627
setupPredictMarketDetailsTest(marketWithPartialResolution);
23912628

2392-
expect(
2393-
screen.queryByTestId('predict-details-chart'),
2394-
).not.toBeOnTheScreen();
2629+
expect(screen.getByTestId('predict-details-chart')).toBeOnTheScreen();
23952630
});
23962631

23972632
it('displays resolved outcomes count badge', () => {

0 commit comments

Comments
 (0)