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

Update item name feature to handle HEAR rebate projects #187

Merged
merged 4 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cypress/e2e/state-calculator.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ describe('rewiring-america-state-calculator', () => {
.checkA11y(null, {
runOnly: ['wcag2a', 'wcag2aa'],
});

cy.selectProjects(['cooking']);

cy.get('rewiring-america-state-calculator')
.shadow()
.contains('Up to $420 off an electric/induction stove');
});

it('shows an error if you query in the wrong state', () => {
Expand Down
48 changes: 44 additions & 4 deletions src/item-name.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ItemType } from './api/calculator-types-v1';
import { MsgFn } from './i18n/use-translated';
import { Project } from './projects';

type ItemGroup =
| 'air_source_heat_pump'
Expand All @@ -14,7 +15,8 @@ type ItemGroup =
| 'weatherization'
| 'audit_and_weatherization'
| 'water_heater'
| 'electric_outdoor_equipment';
| 'electric_outdoor_equipment'
| 'hear_projects';

const ALL_INSULATION: ItemType[] = [
'attic_or_roof_insulation',
Expand Down Expand Up @@ -114,13 +116,17 @@ const ITEM_GROUPS: { group: ItemGroup; members: Set<ItemType> }[] = [
group: 'water_heater',
members: new Set(['heat_pump_water_heater', 'non_heat_pump_water_heater']),
},
{
group: 'hear_projects',
members: new Set(['heat_pump_clothes_dryer', 'electric_stove']),
},
];

const itemsBelongToGroup = (items: ItemType[], members: Set<ItemType>) => {
return items.every(i => members.has(i));
};

const multipleItemsName = (items: ItemType[], msg: MsgFn) => {
const multipleItemsName = (items: ItemType[], msg: MsgFn, project: Project) => {
// For a multiple-items case, check whether all the items are in one of the
// defined groups.
for (const { group, members } of ITEM_GROUPS) {
Expand Down Expand Up @@ -174,6 +180,8 @@ const multipleItemsName = (items: ItemType[], msg: MsgFn) => {
return msg('electric outdoor equipment', {
desc: 'e.g. "$100 off [this string]"',
});
case 'hear_projects':
return hearName(items, msg, project);
default: {
// This will be a type error if the above switch is not exhaustive
const unknownGroup: never = group;
Expand All @@ -186,12 +194,44 @@ const multipleItemsName = (items: ItemType[], msg: MsgFn) => {
return null;
};

const hearName = (items: ItemType[], msg: MsgFn, project: Project) => {
const HEAR_INCENTIVE_PROJECT_MSG_LIST: {
item: ItemType;
project: Project;
msg: string;
}[] = [
{
item: 'heat_pump_clothes_dryer',
project: 'clothes_dryer',
msg: msg('a heat pump clothes dryer', {
desc: 'e.g. "$100 off [this string]"',
}),
},

{
item: 'electric_stove',
project: 'cooking',
msg: msg('an electric/induction stove', {
desc: 'e.g. "$100 off [this string]"',
}),
},
];

const match = HEAR_INCENTIVE_PROJECT_MSG_LIST.find(
group => items.includes(group.item) && project === group.project,
);

if (!match) return null;

return match.msg;
};

/**
* TODO this is an internationalization sin. Figure out something better!
*/
export const itemName = (items: ItemType[], msg: MsgFn) => {
export const itemName = (items: ItemType[], msg: MsgFn, project: Project) => {
if (items.length > 1) {
return multipleItemsName(items, msg);
return multipleItemsName(items, msg, project);
}

if (items.length !== 1) {
Expand Down
14 changes: 10 additions & 4 deletions src/state-incentive-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const formatUnit = (unit: AmountUnit, msg: MsgFn) =>
? msg('watt')
: unit;

const formatTitle = (incentive: Incentive, msg: MsgFn) => {
const item = itemName(incentive.items, msg);
const formatTitle = (incentive: Incentive, msg: MsgFn, project: Project) => {
const item = itemName(incentive.items, msg, project);
if (!item) {
return null;
}
Expand Down Expand Up @@ -145,6 +145,7 @@ const renderCardCollection = (
incentives: Incentive[],
iraRebates: IRARebate[],
showComingSoon: boolean,
project: Project,
) => {
const { msg } = useTranslated();
return (
Expand All @@ -156,7 +157,7 @@ const renderCardCollection = (
(getStartYearIfInFuture(a) ?? 0) - (getStartYearIfInFuture(b) ?? 0),
)
.map((incentive, index) => {
const headline = formatTitle(incentive, msg);
const headline = formatTitle(incentive, msg, project);
if (!headline) {
// We couldn't generate a headline either because the items are
// unknown, or the amount type is unknown. Don't show a card.
Expand Down Expand Up @@ -271,7 +272,12 @@ const IncentiveGrid = forwardRef<HTMLDivElement, IncentiveGridProps>(
/>
</div>
{selectedTab !== null
? renderCardCollection(incentives, iraRebates, !hasStateCoverage)
? renderCardCollection(
incentives,
iraRebates,
!hasStateCoverage,
selectedTab,
)
: renderSelectProjectCard()}
</>
);
Expand Down
88 changes: 68 additions & 20 deletions test/item-names.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,80 @@ import { itemName } from '../src/item-name';

describe('group names', () => {
test('heat pumps', () => {
expect(itemName(['ducted_heat_pump', 'ductless_heat_pump'], msg)).toBe(
'an air source heat pump',
);
expect(
itemName(['ducted_heat_pump', 'geothermal_heating_installation'], msg),
itemName(['ducted_heat_pump', 'ductless_heat_pump'], msg, 'hvac'),
).toBe('an air source heat pump');
expect(
itemName(
['ducted_heat_pump', 'geothermal_heating_installation'],
msg,
'hvac',
),
).toBe('a heat pump');
});

test('weatherization and insulation', () => {
expect(
itemName(['attic_or_roof_insulation', 'basement_insulation'], msg),
itemName(
['attic_or_roof_insulation', 'basement_insulation'],
msg,
'weatherization_and_efficiency',
),
).toBe('insulation');
expect(itemName(['attic_or_roof_insulation', 'air_sealing'], msg)).toBe(
'weatherization',
);
expect(itemName(['wall_insulation', 'other_insulation'], msg)).toBe(
'insulation',
);
expect(itemName(['wall_insulation', 'other_weatherization'], msg)).toBe(
'weatherization',
);
expect(itemName(['other_weatherization', 'energy_audit'], msg)).toBe(
'an energy audit and weatherization',
);
expect(
itemName(
['attic_or_roof_insulation', 'air_sealing'],
msg,
'weatherization_and_efficiency',
),
).toBe('weatherization');
expect(
itemName(
['wall_insulation', 'other_insulation'],
msg,
'weatherization_and_efficiency',
),
).toBe('insulation');
expect(
itemName(
['wall_insulation', 'other_weatherization'],
msg,
'weatherization_and_efficiency',
),
).toBe('weatherization');
expect(
itemName(
['other_weatherization', 'energy_audit'],
msg,
'weatherization_and_efficiency',
),
).toBe('an energy audit and weatherization');
});

test('vehicles', () => {
expect(
itemName(['new_electric_vehicle', 'used_electric_vehicle'], msg),
itemName(['new_electric_vehicle', 'used_electric_vehicle'], msg, 'ev'),
).toBe('an electric vehicle');
expect(
itemName(
['new_plugin_hybrid_vehicle', 'used_plugin_hybrid_vehicle'],
msg,
'ev',
),
).toBe('a plug-in hybrid');
expect(
itemName(['new_electric_vehicle', 'new_plugin_hybrid_vehicle'], msg),
itemName(
['new_electric_vehicle', 'new_plugin_hybrid_vehicle'],
msg,
'ev',
),
).toBe('a new vehicle');
expect(
itemName(['used_electric_vehicle', 'used_plugin_hybrid_vehicle'], msg),
itemName(
['used_electric_vehicle', 'used_plugin_hybrid_vehicle'],
msg,
'ev',
),
).toBe('a used vehicle');

expect(
Expand All @@ -56,7 +89,22 @@ describe('group names', () => {
'used_plugin_hybrid_vehicle',
],
msg,
'ev',
),
).toBeNull();
});

test('HEAR rebates applicable to multiple appliances', () => {
expect(
itemName(['heat_pump_clothes_dryer', 'electric_stove'], msg, 'cooking'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Should 'heat_pump_clothes_dryer' be included here?

).toBe('an electric/induction stove');

expect(
itemName(
['heat_pump_clothes_dryer', 'electric_stove'],
msg,
'clothes_dryer',
),
).toBe('a heat pump clothes dryer');
});
});
Loading