The canvas had a 1px frame. The company logos are opaque white, so invert them to match the dark tiles. Core fetches the Auth public key (JWKS), so the To Be Played diagram now shows that arrow.
206 lines
8.1 KiB
TypeScript
206 lines
8.1 KiB
TypeScript
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
import { provideZonelessChangeDetection } from '@angular/core';
|
|
import { By, DomSanitizer } from '@angular/platform-browser';
|
|
import { MatIconRegistry } from '@angular/material/icon';
|
|
import { provideRouter } from '@angular/router';
|
|
import { provideTranslateService } from '@ngx-translate/core';
|
|
import { AboutComponent } from './about.component';
|
|
import { AssetsConstants } from '../../constants/AssetsConstants';
|
|
|
|
/**
|
|
* No translations are loaded, so the translate pipe echoes the key back. That is
|
|
* exactly what these tests want: they assert which key is rendered where, which
|
|
* is what the mismatched skill headings got wrong. Expected values are written
|
|
* out literally rather than read off the component, so a change to the component
|
|
* cannot silently move the assertion with it.
|
|
*/
|
|
describe('AboutComponent', () => {
|
|
let fixture: ComponentFixture<AboutComponent>;
|
|
|
|
beforeEach(async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [AboutComponent],
|
|
providers: [
|
|
provideZonelessChangeDetection(),
|
|
provideRouter([]),
|
|
provideTranslateService({ lang: 'en', fallbackLang: 'en' }),
|
|
],
|
|
}).compileComponents();
|
|
|
|
registerStubIcons();
|
|
|
|
fixture = TestBed.createComponent(AboutComponent);
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
/**
|
|
* The real SVG icons are registered by an app initializer that TestBed never
|
|
* runs, so mat-icon logs a lookup error for every fixture. Literals keep the
|
|
* registry satisfied without pulling HttpClient into these tests.
|
|
*/
|
|
function registerStubIcons(): void {
|
|
const iconRegistry = TestBed.inject(MatIconRegistry);
|
|
const sanitizer = TestBed.inject(DomSanitizer);
|
|
const emptyIcon = sanitizer.bypassSecurityTrustHtml('<svg></svg>');
|
|
|
|
iconRegistry.addSvgIconLiteral('linkedin', emptyIcon);
|
|
iconRegistry.addSvgIconLiteral('github', emptyIcon);
|
|
}
|
|
|
|
function textOf(selector: string): string[] {
|
|
return fixture.debugElement
|
|
.queryAll(By.css(selector))
|
|
.map(element => element.nativeElement.textContent.trim());
|
|
}
|
|
|
|
function click(testId: string): void {
|
|
fixture.debugElement
|
|
.query(By.css(`[data-testid="${testId}"]`))
|
|
.nativeElement.click();
|
|
fixture.detectChanges();
|
|
}
|
|
|
|
function skillGroupTexts(): { heading: string; chips: string[] }[] {
|
|
return fixture.debugElement.queryAll(By.css('mat-chip-set')).map(chipSet => {
|
|
const group = chipSet.parent!;
|
|
const heading = group.query(By.css('h3')).nativeElement.textContent.trim();
|
|
const chips = group
|
|
.queryAll(By.css('mat-chip'))
|
|
.map(chip => chip.nativeElement.textContent.trim());
|
|
return { heading, chips };
|
|
});
|
|
}
|
|
|
|
describe('skills', () => {
|
|
it('renders the skill groups in the documented order', () => {
|
|
expect(skillGroupTexts().map(group => group.heading)).toEqual([
|
|
'ABOUT.SECTION.LANGUAGES',
|
|
'ABOUT.SECTION.ARCH_CLOUD',
|
|
'ABOUT.SECTION.SIM_ALGO',
|
|
'ABOUT.SECTION.AI_ENG',
|
|
]);
|
|
});
|
|
|
|
it('lists languages under the languages heading, not under architecture', () => {
|
|
const languages = skillGroupTexts().find(group => group.heading === 'ABOUT.SECTION.LANGUAGES');
|
|
|
|
expect(languages).toBeDefined();
|
|
expect(languages!.chips).toContain('ABOUT.SKILLS.JAVA');
|
|
expect(languages!.chips).not.toContain('ABOUT.TOOLS.DOCKER');
|
|
});
|
|
|
|
it('lists infrastructure tools under the architecture heading', () => {
|
|
const architecture = skillGroupTexts().find(group => group.heading === 'ABOUT.SECTION.ARCH_CLOUD');
|
|
|
|
expect(architecture).toBeDefined();
|
|
expect(architecture!.chips).toContain('ABOUT.TOOLS.DOCKER');
|
|
expect(architecture!.chips).not.toContain('ABOUT.SKILLS.JAVA');
|
|
});
|
|
|
|
it('advertises AI-assisted engineering as its own group', () => {
|
|
const ai = skillGroupTexts().find(group => group.heading === 'ABOUT.SECTION.AI_ENG');
|
|
|
|
expect(ai).toBeDefined();
|
|
expect(ai!.chips).toContain('ABOUT.SKILLS.AI_HARNESS');
|
|
});
|
|
});
|
|
|
|
describe('experience', () => {
|
|
it('hides the pre-2010 positions until they are requested', () => {
|
|
expect(textOf('[data-testid="role"]')).toContain('ABOUT.XP.COMPANY9.ROLE');
|
|
expect(textOf('[data-testid="role"]')).not.toContain('ABOUT.XP.COMPANY0.ROLE');
|
|
});
|
|
|
|
it('inverts the opaque white logos so they read as dark tiles', () => {
|
|
const inverted = fixture.debugElement
|
|
.queryAll(By.css('img.invert'))
|
|
.map(img => new URL(img.nativeElement.src, 'http://localhost').pathname);
|
|
const expected = [AssetsConstants.COLORDIGITAL_LOGO, AssetsConstants.TERAPORT_LOGO]
|
|
.map(src => new URL(src, 'http://localhost/').pathname);
|
|
|
|
expect(new Set(inverted)).toEqual(new Set(expected));
|
|
});
|
|
|
|
it('reveals the earlier positions when the toggle button is clicked', () => {
|
|
click('toggle-experience');
|
|
|
|
expect(textOf('[data-testid="role"]')).toContain('ABOUT.XP.COMPANY0.ROLE');
|
|
});
|
|
|
|
it('reports the expanded state of the experience toggle to assistive technology', () => {
|
|
const toggle = fixture.debugElement.query(By.css('[data-testid="toggle-experience"]')).nativeElement;
|
|
expect(toggle.getAttribute('aria-expanded')).toBe('false');
|
|
|
|
click('toggle-experience');
|
|
|
|
expect(toggle.getAttribute('aria-expanded')).toBe('true');
|
|
});
|
|
|
|
it('draws the employer header once per run of consecutive roles', () => {
|
|
const companies = textOf('[data-testid="company"]');
|
|
|
|
// COMPANY5/4/3 are three consecutive Assyst roles and share one header.
|
|
expect(companies).toContain('ABOUT.XP.COMPANY5.COMPANY');
|
|
expect(companies).not.toContain('ABOUT.XP.COMPANY4.COMPANY');
|
|
expect(companies).not.toContain('ABOUT.XP.COMPANY3.COMPANY');
|
|
});
|
|
|
|
it('treats the two ColorDigital spells as separate blocks so the timeline stays chronological', () => {
|
|
const companies = textOf('[data-testid="company"]');
|
|
|
|
expect(companies).toContain('ABOUT.XP.COMPANY9.COMPANY');
|
|
expect(companies).toContain('ABOUT.XP.COMPANY7.COMPANY');
|
|
});
|
|
|
|
it('keeps the employer header at the seam when the earlier roles are appended', () => {
|
|
click('toggle-experience');
|
|
|
|
// COMPANY3 (Assyst) and COMPANY2 (TH Bingen) meet at the seam, so the
|
|
// first earlier role must still get its own header.
|
|
expect(textOf('[data-testid="company"]')).toContain('ABOUT.XP.COMPANY2.COMPANY');
|
|
});
|
|
});
|
|
|
|
describe('projects', () => {
|
|
it('lists To Be Played and links to its project dialog', () => {
|
|
expect(textOf('mat-card strong')).toContain('ABOUT.PROJECT.TBP.TITLE');
|
|
|
|
const hrefs = fixture.debugElement
|
|
.queryAll(By.css('a[href]'))
|
|
.map(anchor => anchor.nativeElement.getAttribute('href'));
|
|
|
|
expect(hrefs).toContain('/projects?project=to-be-played');
|
|
expect(hrefs).toContain('https://tbp.andreas-dahm.eu');
|
|
});
|
|
});
|
|
|
|
describe('education', () => {
|
|
it('shows only the post-school entries until the toggle button is clicked', () => {
|
|
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E6.WHERE');
|
|
expect(textOf('[data-testid="education-where"]')).not.toContain('ABOUT.EDUCATION.E2.WHERE');
|
|
expect(textOf('[data-testid="education-where"]').length).toBe(3);
|
|
|
|
click('toggle-education');
|
|
|
|
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E2.WHERE');
|
|
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E0.WHERE');
|
|
expect(textOf('[data-testid="education-where"]').length).toBe(7);
|
|
});
|
|
});
|
|
|
|
describe('call to action', () => {
|
|
it('offers no download link, so nothing can point at a missing file', () => {
|
|
expect(fixture.debugElement.queryAll(By.css('a[download]')).length).toBe(0);
|
|
});
|
|
|
|
it('exposes the mail contact as a button rather than an empty link', () => {
|
|
const emptyLinks = fixture.debugElement
|
|
.queryAll(By.css('a'))
|
|
.filter(anchor => anchor.nativeElement.getAttribute('href') === '');
|
|
|
|
expect(emptyLinks.length).toBe(0);
|
|
expect(fixture.debugElement.query(By.css('[data-testid="contact-mail"]'))).toBeTruthy();
|
|
});
|
|
});
|
|
});
|