Skip to content

Commit 65d2941

Browse files
authored
Merge pull request #265 from PolicyEngine/feat/current-law-selector
Add current law selector to sim setup
2 parents aee8c07 + a62d6f6 commit 65d2941

File tree

6 files changed

+829
-2
lines changed

6 files changed

+829
-2
lines changed

app/src/flows/simulationCreationFlow.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const SimulationCreationFlow: Flow = {
2525
returnTo: 'SimulationSetupFrame',
2626
},
2727
loadExisting: 'SimulationSelectExistingPolicyFrame',
28+
selectCurrentLaw: 'SimulationSetupFrame',
2829
},
2930
},
3031
SimulationSelectExistingPolicyFrame: {

app/src/frames/simulation/SimulationSetupPolicyFrame.tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import { useState } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
23
import FlowView from '@/components/common/FlowView';
4+
import { useCurrentCountry } from '@/hooks/useCurrentCountry';
5+
import { selectCurrentPosition } from '@/reducers/activeSelectors';
6+
import { createPolicyAtPosition } from '@/reducers/policyReducer';
7+
import { RootState } from '@/store';
38
import { FlowComponentProps } from '@/types/flow';
49

5-
type SetupAction = 'createNew' | 'loadExisting';
10+
type SetupAction = 'createNew' | 'loadExisting' | 'selectCurrentLaw';
611

712
export default function SimulationSetupPolicyFrame({ onNavigate }: FlowComponentProps) {
13+
const dispatch = useDispatch();
14+
const country = useCurrentCountry();
15+
const currentPosition = useSelector((state: RootState) => selectCurrentPosition(state));
16+
const currentLawId = useSelector((state: RootState) => state.metadata.currentLawId);
17+
818
const [selectedAction, setSelectedAction] = useState<SetupAction | null>(null);
919

1020
function handleClickCreateNew() {
@@ -15,13 +25,42 @@ export default function SimulationSetupPolicyFrame({ onNavigate }: FlowComponent
1525
setSelectedAction('loadExisting');
1626
}
1727

28+
function handleClickCurrentLaw() {
29+
setSelectedAction('selectCurrentLaw');
30+
}
31+
32+
function handleSubmitCurrentLaw() {
33+
// Create current law policy at the current position
34+
dispatch(
35+
createPolicyAtPosition({
36+
position: currentPosition,
37+
policy: {
38+
id: currentLawId.toString(),
39+
label: 'Current law',
40+
parameters: [], // Empty parameters = current law
41+
isCreated: true, // Already exists (it's the baseline)
42+
countryId: country,
43+
},
44+
})
45+
);
46+
}
47+
1848
function handleClickSubmit() {
19-
if (selectedAction) {
49+
if (selectedAction === 'selectCurrentLaw') {
50+
handleSubmitCurrentLaw();
51+
onNavigate(selectedAction);
52+
} else if (selectedAction) {
2053
onNavigate(selectedAction);
2154
}
2255
}
2356

2457
const buttonPanelCards = [
58+
{
59+
title: 'Current Law',
60+
description: 'Use the baseline tax-benefit system with no reforms',
61+
onClick: handleClickCurrentLaw,
62+
isSelected: selectedAction === 'selectCurrentLaw',
63+
},
2564
{
2665
title: 'Load Existing Policy',
2766
description: 'Use a policy you have already created',
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { vi } from 'vitest';
2+
import { RootState } from '@/store';
3+
4+
// Test constants
5+
export const TEST_CURRENT_LAW_IDS = {
6+
US: 2,
7+
UK: 1,
8+
} as const;
9+
10+
export const TEST_COUNTRIES = {
11+
US: 'us',
12+
UK: 'uk',
13+
} as const;
14+
15+
// Button order constants for testing
16+
export const BUTTON_ORDER = {
17+
CURRENT_LAW: 0,
18+
LOAD_EXISTING: 1,
19+
CREATE_NEW: 2,
20+
} as const;
21+
22+
export const BUTTON_TEXT = {
23+
CURRENT_LAW: {
24+
title: 'Current Law',
25+
description: 'Use the baseline tax-benefit system with no reforms',
26+
},
27+
LOAD_EXISTING: {
28+
title: 'Load Existing Policy',
29+
description: 'Use a policy you have already created',
30+
},
31+
CREATE_NEW: {
32+
title: 'Create New Policy',
33+
description: 'Build a new policy',
34+
},
35+
} as const;
36+
37+
// Mock navigation function
38+
export const mockOnNavigate = vi.fn();
39+
40+
// Mock dispatch function
41+
export const mockDispatch = vi.fn();
42+
43+
// Helper to create mock Redux state for SimulationSetupPolicyFrame
44+
export const createMockSimulationSetupPolicyState = (overrides?: {
45+
countryId?: string;
46+
currentLawId?: number;
47+
mode?: 'standalone' | 'report';
48+
activeSimulationPosition?: 0 | 1;
49+
}): Partial<RootState> => {
50+
const {
51+
countryId = TEST_COUNTRIES.US,
52+
currentLawId = TEST_CURRENT_LAW_IDS.US,
53+
mode = 'standalone',
54+
activeSimulationPosition = 0,
55+
} = overrides || {};
56+
57+
return {
58+
metadata: {
59+
currentCountry: countryId,
60+
loading: false,
61+
error: null,
62+
variables: {},
63+
parameters: {},
64+
entities: {},
65+
variableModules: {},
66+
economyOptions: { region: [], time_period: [], datasets: [] },
67+
currentLawId,
68+
basicInputs: [],
69+
modelledPolicies: { core: {}, filtered: {} },
70+
version: '1.0.0',
71+
parameterTree: null,
72+
},
73+
report: {
74+
id: '',
75+
label: null,
76+
countryId: countryId as any,
77+
apiVersion: null,
78+
simulationIds: [],
79+
status: 'pending',
80+
output: null,
81+
createdAt: new Date().toISOString(),
82+
updatedAt: new Date().toISOString(),
83+
activeSimulationPosition,
84+
mode,
85+
},
86+
policy: {
87+
policies: [null, null],
88+
},
89+
};
90+
};
91+
92+
// Expected policy payloads for current law
93+
export const expectedCurrentLawPolicyUS = {
94+
id: TEST_CURRENT_LAW_IDS.US.toString(),
95+
label: 'Current law',
96+
parameters: [],
97+
isCreated: true,
98+
countryId: TEST_COUNTRIES.US,
99+
};
100+
101+
export const expectedCurrentLawPolicyUK = {
102+
id: TEST_CURRENT_LAW_IDS.UK.toString(),
103+
label: 'Current law',
104+
parameters: [],
105+
isCreated: true,
106+
countryId: TEST_COUNTRIES.UK,
107+
};
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { vi } from 'vitest';
2+
import { countryIds } from '@/libs/countries';
3+
import type { Population } from '@/types/ingredients/Population';
4+
5+
// Test constants for current law integration tests
6+
export const INTEGRATION_TEST_COUNTRIES = {
7+
US: 'us',
8+
UK: 'uk',
9+
} as const;
10+
11+
export const INTEGRATION_TEST_CURRENT_LAW_IDS = {
12+
US: 2,
13+
UK: 1,
14+
} as const;
15+
16+
// Mock metadata for US with current law ID
17+
export const mockUSMetadata = {
18+
status: 'success',
19+
message: null,
20+
result: {
21+
variables: {},
22+
parameters: {},
23+
entities: {},
24+
variableModules: {},
25+
economy_options: {
26+
region: [{ name: 'us', label: 'United States' }],
27+
time_period: [{ name: 2025, label: '2025' }],
28+
datasets: [],
29+
},
30+
current_law_id: INTEGRATION_TEST_CURRENT_LAW_IDS.US,
31+
basicInputs: [],
32+
modelled_policies: { core: {}, filtered: {} },
33+
version: '1.0.0',
34+
},
35+
};
36+
37+
// Mock metadata for UK with current law ID
38+
export const mockUKMetadata = {
39+
status: 'success',
40+
message: null,
41+
result: {
42+
variables: {},
43+
parameters: {},
44+
entities: {},
45+
variableModules: {},
46+
economy_options: {
47+
region: [{ name: 'uk', label: 'United Kingdom' }],
48+
time_period: [{ name: 2025, label: '2025' }],
49+
datasets: [],
50+
},
51+
current_law_id: INTEGRATION_TEST_CURRENT_LAW_IDS.UK,
52+
basicInputs: [],
53+
modelled_policies: { core: {}, filtered: {} },
54+
version: '1.0.0',
55+
},
56+
};
57+
58+
// Expected current law policy objects after creation
59+
export const expectedCurrentLawPolicyUS = {
60+
id: INTEGRATION_TEST_CURRENT_LAW_IDS.US.toString(),
61+
label: 'Current law',
62+
parameters: [],
63+
isCreated: true,
64+
countryId: INTEGRATION_TEST_COUNTRIES.US,
65+
};
66+
67+
export const expectedCurrentLawPolicyUK = {
68+
id: INTEGRATION_TEST_CURRENT_LAW_IDS.UK.toString(),
69+
label: 'Current law',
70+
parameters: [],
71+
isCreated: true,
72+
countryId: INTEGRATION_TEST_COUNTRIES.UK,
73+
};
74+
75+
// Mock population for simulation setup
76+
export const mockPopulation: Population = {
77+
label: 'Test Population',
78+
isCreated: true,
79+
household: {
80+
id: 'household-123',
81+
countryId: 'us' as (typeof countryIds)[number],
82+
householdData: {
83+
people: {},
84+
},
85+
},
86+
geography: null,
87+
};
88+
89+
// Helper to create mock fetch implementation for metadata
90+
export const createMetadataFetchMock = (country: string) => {
91+
return vi.fn().mockImplementation((url: string) => {
92+
if (url.includes(`/${country}/metadata`)) {
93+
const metadata = country === 'us' ? mockUSMetadata : mockUKMetadata;
94+
return Promise.resolve({
95+
ok: true,
96+
status: 200,
97+
json: () => Promise.resolve(metadata),
98+
});
99+
}
100+
return Promise.resolve({
101+
ok: false,
102+
status: 404,
103+
json: () => Promise.resolve({ error: 'Not found' }),
104+
});
105+
});
106+
};

0 commit comments

Comments
 (0)