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

SFD-175 Tab to switch between form and assumptions view #123

Merged
merged 8 commits into from
Aug 21, 2024
10 changes: 4 additions & 6 deletions playwright/tests/test_17_EstTool_UI_AssumpLimitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ def test_example(page: Page) -> None:

# Not recording calcs in this test. Content is here to check link and content of "Assumptions and limitations" page

expect(page.get_by_role("button", name="Assumptions and limitations")).to_be_visible()
page.get_by_role("button", name="Assumptions and limitations").click()
expect(page.get_by_role("tab", name="Assumptions and limitations")).to_be_visible()
page.get_by_role("tab", name="Assumptions and limitations").click()
expect(page.get_by_role("heading", name="Assumptions and Limitations")).to_be_visible()
expect(page.get_by_label("Close assumptions and").filter(has_text=re.compile("close"))).to_be_visible() # 'X' close button
# Need regex because has_text="close" is case insensitive and other close button has the text 'Close'
expect(page.get_by_role("tab", name="Estimation Input")).to_be_visible()
expect(page.get_by_text("The Technology Carbon Estimator tool is designed to")).to_be_visible()
expect(page.get_by_role("heading", name="Assumptions", exact=True)).to_be_visible()
expect(page.get_by_role("heading", name="Time period")).to_be_visible()
Expand Down Expand Up @@ -114,8 +113,7 @@ def test_example(page: Page) -> None:
expect(page.get_by_text("Like Off the shelf and open")).to_be_visible()
expect(page.get_by_role("heading", name="Managed Services")).to_be_visible()
expect(page.get_by_text("We currently do not make a")).to_be_visible()
expect(page.get_by_label("Close assumptions and").filter(has_text=re.compile("Close"))).to_be_visible()
page.get_by_label("Close assumptions and").filter(has_text=re.compile("Close")).click()
page.get_by_role("tab", name="Estimation Input").click()
expect(page.get_by_role("heading", name="Technology Carbon Estimator")).to_be_visible()
expect(page.get_by_role("heading", name="Organisation")).to_be_visible()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
<div #assumptionsLimitation class="tce-flex tce-flex-col tce-gap-4" tabindex="0">
<div class="tce-flex tce-flex-col tce-gap-4" tabindex="0">
<div class="tce-flex tce-justify-between">
<h2 class="tce-text-2xl">Assumptions and Limitations</h2>
<button
type="button"
class="material-icons-outlined tce-text hover:tce-bg-slate-200 hover:tce-rounded"
(click)="onClose()"
aria-label="Close assumptions and limitations">
close
</button>
</div>
<p class="tce-text-sm">
The Technology Carbon Estimator tool is designed to give a high-level overview of the possible areas of carbon
Expand Down Expand Up @@ -117,7 +110,13 @@ <h4 class="tce-text-lg">Carbon Intensity</h4>
<h4 class="tce-text-lg">Upstream Emissions</h4>
<p class="tce-text-sm">
Each class of device is given an average amount of embodied carbon and a lifespan, based on averages taken from
manufacturer provided Product Carbon Footprint documentation<a id="networkNoteRef" role="doc-noteref" href="#networkNote" aria-label="see footnote">*</a>. At present these are:
manufacturer provided Product Carbon Footprint documentation<a
id="networkNoteRef"
role="doc-noteref"
href="#networkNote"
aria-label="see footnote"
>*</a
>. At present these are:
</p>
<table class="tce-w-fit">
<thead>
Expand All @@ -144,8 +143,8 @@ <h4 class="tce-text-lg">Upstream Emissions</h4>
</tbody>
</table>
<aside id="networkNote" class="tce-text-sm tce-italic" role="doc-footnote">
<a role="doc-backlink" href="#networkNoteRef" aria-label="return to reference">*</a> The network device figure is reverse engineered from typical yearly energy and embodied carbon ratios as published
values are difficult to find.
<a role="doc-backlink" href="#networkNoteRef" aria-label="return to reference">*</a> The network device figure is
reverse engineered from typical yearly energy and embodied carbon ratios as published values are difficult to find.
</aside>
<p class="tce-text-sm">
We use the total Embodied Carbon and the lifespan to give an
Expand Down Expand Up @@ -322,10 +321,4 @@ <h5 class="tce-text-base">Managed Services</h5>
<p class="tce-text-sm">
We currently do not make a distinction between on-premises data centers and those run by a third party.
</p>
<button
class="tce-button-close tce-px-3 tce-py-2 tce-self-end"
(click)="onClose()"
aria-label="Close assumptions and limitations">
Close
</button>
</div>
Original file line number Diff line number Diff line change
@@ -1,53 +1,52 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AssumptionsAndLimitationComponent } from './assumptions-and-limitation.component';
import { CarbonIntensityService } from '../services/carbon-intensity.service';
import { WorldLocation } from '../types/carbon-estimator';
import { gCo2ePerKwh } from '../types/units';

const testIntensities: Record<WorldLocation, gCo2ePerKwh> = {
WORLD: 100,
GBR: 200,
EUROPE: 300,
'NORTH AMERICA': 400,
ASIA: 500,
AFRICA: 600,
OCEANIA: 700,
'LATIN AMERICA AND CARIBBEAN': 800,
};

describe('AssumptionsAndLimitationComponent', () => {
let component: AssumptionsAndLimitationComponent;
let fixture: ComponentFixture<AssumptionsAndLimitationComponent>;
let intensityServiceStub: CarbonIntensityService;

beforeEach(async () => {
intensityServiceStub = {
getCarbonIntensity: location => testIntensities[location],
};

await TestBed.configureTestingModule({
imports: [AssumptionsAndLimitationComponent],
providers: [{ provide: CarbonIntensityService, useValue: intensityServiceStub }],
}).compileComponents();

fixture = TestBed.createComponent(AssumptionsAndLimitationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should emit close event when click close button', () => {
spyOn(component.closeEvent, 'emit');

fixture.nativeElement.querySelector('button').click();

expect(component.closeEvent.emit).toHaveBeenCalledTimes(1);
});

it('should emit close event when press esc key', () => {
spyOn(component.closeEvent, 'emit');

document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));

expect(component.closeEvent.emit).toHaveBeenCalledTimes(1);
});

it('should have focus afterContentInit', () => {
component.ngAfterContentInit();
fixture.detectChanges();

const hasFocus = component.assumptionsLimitation.nativeElement.contains(document.activeElement);
expect(hasFocus).toBeTrue();
});

it('should emit close event with true value when press esc key with focus within component', () => {
spyOn(component.closeEvent, 'emit');

component.ngAfterContentInit();
fixture.detectChanges();

document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));

expect(component.closeEvent.emit).toHaveBeenCalledWith(true);
it('should expose carbon intensity values', () => {
expect(component).toBeTruthy();
expect(component.locationCarbonInfo).toEqual([
{ location: 'Global', carbonIntensity: 100 },
{ location: 'United Kingdom', carbonIntensity: 200 },
{ location: 'Europe', carbonIntensity: 300 },
{ location: 'North America', carbonIntensity: 400 },
{ location: 'Asia', carbonIntensity: 500 },
{ location: 'Africa', carbonIntensity: 600 },
{ location: 'Oceania', carbonIntensity: 700 },
{ location: 'Latin America and Caribbean', carbonIntensity: 800 },
]);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterContentInit, Component, ElementRef, EventEmitter, HostListener, Output, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CLOUD_AVERAGE_PUE, ON_PREMISE_AVERAGE_PUE } from '../estimation/constants';
import { siteTypeInfo } from '../estimation/estimate-downstream-emissions';
import { PurposeOfSite, WorldLocation, locationArray, purposeOfSiteArray } from '../types/carbon-estimator';
Expand Down Expand Up @@ -32,8 +32,7 @@ const locationDescriptions: Record<WorldLocation, string> = {
templateUrl: './assumptions-and-limitation.component.html',
imports: [DecimalPipe, ExternalLinkDirective],
})
export class AssumptionsAndLimitationComponent implements AfterContentInit {
@Output() public closeEvent = new EventEmitter<boolean>();
export class AssumptionsAndLimitationComponent {
readonly ON_PREMISE_AVERAGE_PUE = ON_PREMISE_AVERAGE_PUE;
readonly CLOUD_AVERAGE_PUE = CLOUD_AVERAGE_PUE;
readonly siteTypeInfo = purposeOfSiteArray.map(purpose => ({
Expand Down Expand Up @@ -73,26 +72,10 @@ export class AssumptionsAndLimitationComponent implements AfterContentInit {
},
];

@ViewChild('assumptionsLimitation', { static: true }) public assumptionsLimitation!: ElementRef<HTMLDivElement>;

constructor(private intensityService: CarbonIntensityService) {
this.locationCarbonInfo = locationArray.map(location => ({
location: locationDescriptions[location],
carbonIntensity: this.intensityService.getCarbonIntensity(location),
}));
}

public ngAfterContentInit(): void {
this.assumptionsLimitation.nativeElement.focus({ preventScroll: true });
}

public onClose(hasFocus = true): void {
this.closeEvent.emit(hasFocus);
}

@HostListener('document:keydown.escape', ['$event'])
public onEscKeydown(): void {
const hasFocus = this.assumptionsLimitation.nativeElement.contains(document.activeElement);
this.onClose(hasFocus);
}
}
7 changes: 5 additions & 2 deletions src/app/tab/tabs/tabs.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="tce-w-full tce-flex tce-mb-2">
<div class="tce-w-full tce-flex tce-mb-2" role="tablist">
@for (tab of tabs; track tab.title) {
<div
role="tab"
[class.tce-active-tab]="tab.active()"
(click)="selectTab(tab)"
(keydown.enter)="selectTab(tab)"
Expand All @@ -10,4 +11,6 @@
</div>
}
</div>
<ng-content></ng-content>
<div role="tabpanel">
<ng-content></ng-content>
</div>
30 changes: 11 additions & 19 deletions src/app/tech-carbon-estimator/tech-carbon-estimator.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,17 @@ <h1 class="tce-text-3xl tce-mb-6">Technology Carbon Estimator</h1>
<div class="tce-flex tce-w-full tce-grow tce-flex-wrap lg:tce-flex-nowrap">
<div class="tce-w-full lg:tce-w-1/2 tce-pb-4 lg:tce-pb-0 lg:tce-pr-4">
<disclaimer></disclaimer>
@if (showAssumptionsAndLimitationView) {
<assumptions-and-limitation
#assumptionsLimitation
aria-live="polite"
(closeEvent)="closeAssumptionsAndLimitation($event)"></assumptions-and-limitation>
} @else {
<div class="tce-flex tce-justify-end tce-mb-4 -tce-mt-2">
<button
#showAssumptionsLimitationButton
class="tce-button-assumptions tce-px-3 tce-py-2 tce-w-fit tce-self-end"
(click)="showAssumptionsAndLimitation()">
Assumptions and limitations
</button>
</div>
<carbon-estimator-form
[formValue]="formValue"
(formSubmit)="handleFormSubmit($event)"
(formReset)="handleFormReset()"></carbon-estimator-form>
}
<tabs>
<tab-item [active]="true" [title]="'Estimation Input'">
<carbon-estimator-form
[formValue]="formValue"
(formSubmit)="handleFormSubmit($event)"
(formReset)="handleFormReset()"></carbon-estimator-form>
</tab-item>
<tab-item [title]="'Assumptions and Limitations'">
<assumptions-and-limitation aria-live="polite"></assumptions-and-limitation>
</tab-item>
</tabs>
</div>
<div
#estimations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ describe('TechCarbonEstimatorComponent', () => {
component = fixture.componentInstance;
});

it('should show the form when showAssumptionsAndLimitationsView is false', () => {
component.showAssumptionsAndLimitationView = false;
it('should show the form by default', () => {
fixture.detectChanges();

const formElement = fixture.nativeElement.querySelector('carbon-estimator-form');
Expand All @@ -59,17 +58,6 @@ describe('TechCarbonEstimatorComponent', () => {
expect(assumptionsElement).toBeFalsy();
});

it('should show the assumptions and limitations when showAssumptionsAndLimitationsView is true', () => {
component.showAssumptionsAndLimitationView = true;
fixture.detectChanges();

const formElement = fixture.nativeElement.querySelector('carbon-estimator-form');
const assumptionsElement = fixture.nativeElement.querySelector('assumptions-and-limitation');

expect(formElement).toBeFalsy();
expect(assumptionsElement).toBeTruthy();
});

it('should call estimationService.calculateCarbonEstimation when handleFormSubmit is called', () => {
spyOn(estimationServiceStub, 'calculateCarbonEstimation' as never).and.callThrough();

Expand Down Expand Up @@ -98,16 +86,4 @@ describe('TechCarbonEstimatorComponent', () => {

expect(component.carbonEstimation).toBeNull();
});

it('should focus on assumptions button when closeAssumptionsAndLimitation is called with hasFocus true', () => {
component.showAssumptionsAndLimitationView = true;
fixture.detectChanges();

component.closeAssumptionsAndLimitation(true);
fixture.detectChanges();

const button = component.showAssumptionsLimitationButton.nativeElement;
expect(document.activeElement).toEqual(button);
expect(button);
});
});
20 changes: 4 additions & 16 deletions src/app/tech-carbon-estimator/tech-carbon-estimator.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { AssumptionsAndLimitationComponent } from '../assumptions-and-limitation/assumptions-and-limitation.component';
import { DisclaimerComponent } from '../disclaimer/disclaimer.component';
import { TabsComponent } from '../tab/tabs/tabs.component';
import { TabItemComponent } from '../tab/tab-item/tab-item.component';

@Component({
selector: 'tech-carbon-estimator',
Expand All @@ -18,19 +20,18 @@ import { DisclaimerComponent } from '../disclaimer/disclaimer.component';
CommonModule,
AssumptionsAndLimitationComponent,
DisclaimerComponent,
TabsComponent,
TabItemComponent,
],
templateUrl: './tech-carbon-estimator.component.html',
})
export class TechCarbonEstimatorComponent {
@Input() public extraHeight?: string;

public showAssumptionsAndLimitationView = false;
public formValue: EstimatorValues | undefined;
public carbonEstimation: CarbonEstimation | null = null;

@ViewChild('assumptionsLimitation', { read: ElementRef }) assumptionsLimitation!: ElementRef;
@ViewChild('estimations') estimations!: ElementRef;
@ViewChild('showAssumptionsLimitationButton') showAssumptionsLimitationButton!: ElementRef<HTMLButtonElement>;

constructor(
private estimationService: CarbonEstimationService,
Expand All @@ -47,17 +48,4 @@ export class TechCarbonEstimatorComponent {
public handleFormReset() {
this.carbonEstimation = null;
}

public showAssumptionsAndLimitation(): void {
this.showAssumptionsAndLimitationView = true;
}

public closeAssumptionsAndLimitation(hasFocus: boolean): void {
this.showAssumptionsAndLimitationView = false;
this.changeDetector.detectChanges();
const openButton = this.showAssumptionsLimitationButton.nativeElement;
if (hasFocus) {
openButton.focus();
}
}
}