Skip to content

Commit

Permalink
feat: configure electricity cost (#73)
Browse files Browse the repository at this point in the history
* feat: add electricity cost parameter

* feat: translate the errors of FormValidator

* feat(test): add tests for the electricity cost input
  • Loading branch information
ReidyT authored Dec 4, 2024
1 parent f86b37e commit 8ec348c
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 20 deletions.
4 changes: 3 additions & 1 deletion src/context/SimulationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type SimulationContextType = {
heatLosses: HeatLossPerComponent;
totalHeatLoss: FormattedHeatLoss;
electricityCost: number;
setPricekWh: (newPrice: number) => void;
indoorTemperature: number;
outdoorTemperature: number;
progression: SimulationProgression;
Expand Down Expand Up @@ -79,7 +80,7 @@ export const SimulationProvider = ({
// TODO: These parameters will be changed by the user
const indoorTemperature = SIMULATION_INDOOR_TEMPERATURE_CELCIUS;

const pricekWh = SIMULATION_PRICE_KWH;
const [pricekWh, setPricekWh] = useState(SIMULATION_PRICE_KWH);

const { houseComponentsConfigurator } = useHouseComponents();

Expand Down Expand Up @@ -151,6 +152,7 @@ export const SimulationProvider = ({
totalEnergyConsumptionkWh:
totalHeatLoss / powerConversionFactors.KiloWatt,
}),
setPricekWh,
startSimulation,
}),
[
Expand Down
10 changes: 10 additions & 0 deletions src/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
},
"CLOSE_BUTTON": "Close"
}
},
"ELECTRICITY_CONTROL_PANEL": {
"TITLE": "Electricity Cost",
"ELECTRICITY_COST_LABEL": "Electricity Cost"
}
},
"INSULATIONS": {
Expand All @@ -82,6 +86,12 @@
"Wood": "Wood"
},
"ERROR_BOUNDARY": {
"INPUT_VALIDATOR": {
"Required": "The value is required and cannot be empty.",
"InvalidNumber": "The value must be a valid number.",
"Min": "The value cannot be smaller than {{min}}.",
"Max": "The value cannot be greater than {{max}}."
},
"FALLBACK": {
"MESSAGE_TITLE": "Sorry, something went wrong with this application",
"MESSAGE_FEEDBACK": "Our team has been notified. If you would like to help, please, tell us what happened below.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import {
FormControl,
Expand All @@ -15,6 +16,8 @@ const FORM_ERROR_KEYS = {
Max: 'Max',
} as const;

type FormErrorKeys = keyof typeof FORM_ERROR_KEYS;

export type ValidationRule = {
test: (value: string) => boolean;
message: string;
Expand Down Expand Up @@ -70,7 +73,7 @@ const ValidationRulesFactory = (

if (max !== undefined) {
rules.push({
test: (v: string) => Number.parseFloat(v) < max,
test: (v: string) => Number.parseFloat(v) <= max,
message: FORM_ERROR_KEYS.Max,
});
}
Expand All @@ -90,6 +93,9 @@ export const FormControlValidator = ({
inputType,
unit,
}: FormControlValidatorProps): JSX.Element => {
const { t } = useTranslation('ERROR_BOUNDARY', {
keyPrefix: 'INPUT_VALIDATOR',
});
const [controlledValue, setControlledValue] = useState<string>(String(value));
const [error, setError] = useState<string | undefined>();
const rules = useMemo(
Expand Down Expand Up @@ -119,7 +125,7 @@ export const FormControlValidator = ({
label={label}
value={controlledValue}
onChange={(e) => handleValueChange(e.target.value)}
type={inputType}
type={inputType ?? (validationRules?.isNumber ? 'number' : undefined)}
endAdornment={
unit ? (
<InputAdornment position="end">{unit}</InputAdornment>
Expand All @@ -128,10 +134,13 @@ export const FormControlValidator = ({
/>
{error && (
<FormHelperText
data-testid={`error-${label.toLowerCase()}-${error.toLowerCase()}`}
data-testid={`error-${label.toLowerCase().replace(/ /g, '-')}-${error.toLowerCase()}`}
error
>
{error}
{t(error as FormErrorKeys, {
min: validationRules?.min,
max: validationRules?.max,
})}
</FormHelperText>
)}
</FormControl>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useTranslation } from 'react-i18next';

import { useSimulation } from '@/context/SimulationContext';
import { FormControlValidator } from '@/modules/common/FormControlValidator';

export const ElectricityCostControl = (): JSX.Element => {
const { t } = useTranslation('SIMULATION_CONTROL_PANEL');
const { setPricekWh } = useSimulation();

const handleElectricityCostChange = (newValue: string): void => {
const newPrice = Number.parseFloat(newValue);

if (!Number.isNaN(newPrice)) {
setPricekWh(newPrice);
} else {
console.error(`Invalid price for ${newValue}`);
}
};

return (
<FormControlValidator
label={t('ELECTRICITY_CONTROL_PANEL.ELECTRICITY_COST_LABEL')}
value="0.22"
onChange={(value) => handleElectricityCostChange(value)}
validationRules={{
min: 1e-3,
max: 2,
required: true,
isNumber: true,
}}
unit="CHF/kWh"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';

import { BuildingMaterialKeys } from '@/config/buildingMaterials';
import { FormControlValidator } from '@/modules/common/FormControlValidator';

import { FormControlValidator } from './FormControlValidator';
import { useMaterialControlDialog } from './useMaterialControlDialog';

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,39 @@ import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';

import { ElectricityCostControl } from './ElectricityCostControl';
import { HouseControl } from './HouseControl';

export const SimulationControlPanel = (): JSX.Element => {
const { t } = useTranslation('SIMULATION_CONTROL_PANEL');

return (
<Accordion defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel-house-content"
id="panel-house-header"
>
{t('HOUSE_CONTROL_PANEL.TITLE')}
</AccordionSummary>
<AccordionDetails>
<HouseControl />
</AccordionDetails>
</Accordion>
<>
<Accordion defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel-house-content"
id="panel-house-header"
>
{t('HOUSE_CONTROL_PANEL.TITLE')}
</AccordionSummary>
<AccordionDetails>
<HouseControl />
</AccordionDetails>
</Accordion>

<Accordion defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel-electricity-content"
id="panel-electricity-header"
>
{t('ELECTRICITY_CONTROL_PANEL.TITLE')}
</AccordionSummary>
<AccordionDetails>
<ElectricityCostControl />
</AccordionDetails>
</Accordion>
</>
);
};
21 changes: 20 additions & 1 deletion tests/player/HousePage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Page, expect } from '@playwright/test';
import { Locator, Page, expect } from '@playwright/test';

import { MaterialEditorPage } from './MaterialEditorPage';
import { WindowEditorPage } from './WindowEditorPage';
Expand All @@ -7,8 +7,13 @@ import { WindowEditorPage } from './WindowEditorPage';
export class HousePage {
readonly page: Page;

readonly electricityCost: Locator;

constructor(page: Page) {
this.page = page;
this.electricityCost = this.page.getByRole('spinbutton', {
name: 'Electricity Cost',
});
}

async goto(): Promise<void> {
Expand Down Expand Up @@ -44,4 +49,18 @@ export class HousePage {

return new WindowEditorPage(this.page);
}

async setElectricityCost(newElectricityCost: string): Promise<void> {
await this.electricityCost.click();
await this.electricityCost.fill(newElectricityCost);
await expect(this.electricityCost).toHaveValue(newElectricityCost);
}

async checkErrorIsVisible(
label: string,
type: 'Required' | 'Min' | 'Max',
): Promise<void> {
const errorId = `error-${label.toLowerCase()}-${type.toLowerCase()}`;
await expect(this.page.getByTestId(errorId)).toBeVisible();
}
}
43 changes: 43 additions & 0 deletions tests/player/electricity-cost.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, test } from '@playwright/test';

import { HousePage } from './HousePage';

test('should change electricity cost successfully', async ({ page }) => {
const housePage = new HousePage(page);
await housePage.goto();
await housePage.setElectricityCost('0.33');
});

test('should not accept text', async ({ page }) => {
const housePage = new HousePage(page);
await housePage.goto();
await housePage.electricityCost.click();
await housePage.electricityCost.pressSequentially('Not a number');
await expect(housePage.electricityCost).toHaveValue('');
});

test('should be required', async ({ page }) => {
const housePage = new HousePage(page);
await housePage.goto();
await housePage.electricityCost.click();
await housePage.electricityCost.clear();
await expect(housePage.electricityCost).toHaveValue('');

await housePage.checkErrorIsVisible('electricity-cost', 'Required');
});

test('should not be negative', async ({ page }) => {
const housePage = new HousePage(page);
await housePage.goto();
await housePage.setElectricityCost('-1');

await housePage.checkErrorIsVisible('electricity-cost', 'Min');
});

test('should not exceed max', async ({ page }) => {
const housePage = new HousePage(page);
await housePage.goto();
await housePage.setElectricityCost('5');

await housePage.checkErrorIsVisible('electricity-cost', 'Max');
});
2 changes: 1 addition & 1 deletion tests/player/wall-insulation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ test('thickness should not exceed max value', async ({ page }) => {
const materialEditor = await housePage.openMaterialEditor();
await materialEditor.selectTab('Aerogel');
await materialEditor.setMaterialThickness('25');
await materialEditor.setMaterialThickness('100');
await materialEditor.setMaterialThickness('101');

await materialEditor.checkErrorIsVisible('thickness', 'Max');

Expand Down

0 comments on commit 8ec348c

Please sign in to comment.