Skip to content

Commit

Permalink
feat: add tech to experience items from projects (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlj95 authored May 14, 2024
1 parent 425ec80 commit 8dcd750
Show file tree
Hide file tree
Showing 17 changed files with 226 additions and 38 deletions.
2 changes: 1 addition & 1 deletion assets/resume.json
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@
"endDate": "2022-06-07",
"url": "https://www.koahealth.com/products/mindset",
"roles": ["Senior Software Engineer"],
"entity": "Koa Health / Telefónica Alpha",
"entity": "Koa Health",
"image": "https://davidlj95.com/assets/projects/mindset.png",
"stack": "full",
"technologies": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import {
} from '@/common/relativize-production-url'
import { serviceTestSetup } from '@/test/helpers/service-test-setup'
import { MockProvider } from 'ng-mocks'
import { makeJsonResumeProject } from '../projects-section/__tests__/make-json-resume-project'
import {
ADAPT_JSON_RESUME_PROJECT,
AdaptJsonResumeProject,
} from '../projects-section/adapt-json-resume-project'
import { makeProjectItem } from '../projects-section/__tests__/make-project-item'
import {
JSON_RESUME_PROJECTS,
JsonResumeProjects,
} from '../projects-section/json-resume-projects'

describe('AdaptJsonResumeWork', () => {
it('should be created', () => {
Expand Down Expand Up @@ -96,17 +106,43 @@ describe('AdaptJsonResumeWork', () => {
expect(relativizeProductionUrl).toHaveBeenCalledOnceWith(new URL(image))
expect(item.company.imageSrc).toEqual(dummyImagePath)
})

it('should add projects whose entity matches company name', () => {
const companyName = 'ACME Intl.'
const project = makeJsonResumeProject({ entity: companyName })
const projectItem = makeProjectItem({ name: project.name })
const adaptJsonResumeProject = jasmine
.createSpy<AdaptJsonResumeProject>()
.and.returnValue(projectItem)
const jsonResumeProjects = [project]
const sut = makeSut({ adaptJsonResumeProject, jsonResumeProjects })

const item = sut(makeJsonResumeWork({ name: companyName }))

expect(item.projects).toEqual([projectItem])
expect(adaptJsonResumeProject).toHaveBeenCalledOnceWith(project)
})
})

const makeSut = (
opts: { relativizeProductionUrl?: RelativizeProductionUrl } = {},
opts: {
relativizeProductionUrl?: RelativizeProductionUrl
adaptJsonResumeProject?: AdaptJsonResumeProject
jsonResumeProjects?: JsonResumeProjects
} = {},
): AdaptJsonResumeWork =>
serviceTestSetup(ADAPT_JSON_RESUME_WORK, {
providers: [
MockProvider(
RELATIVIZE_PRODUCTION_URL,
opts.relativizeProductionUrl ?? (() => '/fake/path'),
),
MockProvider(
ADAPT_JSON_RESUME_PROJECT,
opts.adaptJsonResumeProject ??
jasmine.createSpy<AdaptJsonResumeProject>(),
),
MockProvider(JSON_RESUME_PROJECTS, opts.jsonResumeProjects ?? []),
],
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { ExperienceItem } from './experience-item/experience-item'
import { Organization } from '../organization'
import { DateRange } from '../date-range/date-range'
import { RELATIVIZE_PRODUCTION_URL } from '@/common/relativize-production-url'
import { JSON_RESUME_PROJECTS } from '../projects-section/json-resume-projects'
import { ADAPT_JSON_RESUME_PROJECT } from '../projects-section/adapt-json-resume-project'

export type AdaptJsonResumeWork = (work: JsonResumeWork) => ExperienceItem
export const ADAPT_JSON_RESUME_WORK = new InjectionToken<AdaptJsonResumeWork>(
isDevMode ? 'AdaptJsonResumeWork' : 'AJRW',
{
factory: () => {
const projects = inject(JSON_RESUME_PROJECTS)
const adaptProject = inject(ADAPT_JSON_RESUME_PROJECT)
const relativizeUrl = inject(RELATIVIZE_PRODUCTION_URL)
// 👇 JSON Resume Schema of "work"
// https://github.com/jsonresume/resume-schema/blob/v1.0.0/schema.json#L100-L149
Expand All @@ -32,6 +36,9 @@ export const ADAPT_JSON_RESUME_WORK = new InjectionToken<AdaptJsonResumeWork>(
internship: work.internship,
promotions: work.promotions,
morePositions: work.morePositions,
projects: projects
.filter((project) => project.entity === work.name)
.map((project) => adaptProject(project)),
})
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<p>
Extracted from {{ _projectCount }}
{{ _projectCount > 1 ? 'projects' : 'project' }}: {{ _projectNames }}
</p>
<br />
<app-content-chip-list>
<app-content-chip *ngFor="let item of technologies">
<app-technology [item]="item"></app-technology>
</app-content-chip>
</app-content-chip-list>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ComponentFixture } from '@angular/core/testing'

import { ExperienceItemTechComponent } from './experience-item-tech.component'
import { componentTestSetup } from '@/test/helpers/component-test-setup'
import { byComponent } from '@/test/helpers/component-query-predicates'
import { TechnologyComponent } from '../../../technology/technology.component'
import { makeTechnologyItem } from '../../../technology/__tests__/make-technology-item'

describe('ExperienceItemTechComponent', () => {
let component: ExperienceItemTechComponent
let fixture: ComponentFixture<ExperienceItemTechComponent>
const technologies = [
makeTechnologyItem(),
makeTechnologyItem(),
makeTechnologyItem(),
]
const projectNames = ['Project A', 'Project B', 'Project C']

beforeEach(async () => {
;[fixture, component] = makeSut()

component.technologies = technologies
component.projectNames = projectNames

fixture.detectChanges()
})

it('should create', () => {
expect(component).toBeTruthy()
})

it('should display all techs', () => {
const techElements = fixture.debugElement.queryAll(
byComponent(TechnologyComponent),
)
expect(techElements.length).toEqual(technologies.length)
})

it('should display all project names', () => {
expect(fixture.debugElement.nativeElement.textContent).toContain(
'Project A, Project B, and Project C',
)
})
})

const makeSut = () => componentTestSetup(ExperienceItemTechComponent)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Component, Input } from '@angular/core'
import { TechnologyItem } from '../../../technology/technology-item'
import { ContentChipComponent } from '../../../content-chip/content-chip.component'
import { ContentChipListComponent } from '../../../content-chip-list/content-chip-list.component'
import { NgForOf } from '@angular/common'
import { TechnologyComponent } from '../../../technology/technology.component'

@Component({
selector: 'app-experience-item-tech',
standalone: true,
imports: [
ContentChipComponent,
ContentChipListComponent,
NgForOf,
TechnologyComponent,
],
templateUrl: './experience-item-tech.component.html',
styleUrl: './experience-item-tech.component.scss',
})
export class ExperienceItemTechComponent {
@Input({ required: true }) public technologies!: ReadonlyArray<TechnologyItem>
@Input({ required: true }) public set projectNames(
names: ReadonlyArray<string>,
) {
this._projectCount = names.length
this._projectNames = new Intl.ListFormat('en').format(names)
}
protected _projectNames!: string
protected _projectCount!: number
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { TextContentComponent } from '../../chipped-content/text-content/text-co
import { experienceItemToContents } from './experience-item-to-contents'
import { makeExperienceItem } from './__tests__/make-experience-item'
import { ExperienceItemHighlightsComponent } from './experience-item-highlights/experience-item-highlights.component'
import { ProjectItem } from '../../projects-section/project-item/project-item'
import { makeProjectItem } from '../../projects-section/__tests__/make-project-item'
import { makeTechnologyItem } from '../../technology/__tests__/make-technology-item'
import { ExperienceItemTechComponent } from './experience-item-tech/experience-item-tech.component'

describe('experienceItemToContents', () => {
describe('when summary is present', () => {
Expand Down Expand Up @@ -43,6 +47,35 @@ describe('experienceItemToContents', () => {
} satisfies Partial<ExperienceItemHighlightsComponent>)
})
})

describe('when projects technologies are not empty', () => {
const aTechnology = makeTechnologyItem({ slug: 'tech-a' })
const anotherTechnology = makeTechnologyItem({ slug: 'tech-b' })
const technologies = [aTechnology, anotherTechnology]
const projects: ReadonlyArray<ProjectItem> = [
makeProjectItem({ technologies: [aTechnology], name: 'project A' }),
makeProjectItem({
technologies: [aTechnology, anotherTechnology],
name: 'project B',
}),
]

it('should include technologies content with the set of technologies of all projects', () => {
const sut = makeSut()

const contents = sut(makeExperienceItem({ projects }))
const techContents = contents.filter(
(content) => content.displayName === 'Tech',
)
expect(techContents).toHaveSize(1)
const techContent = techContents[0]
expect(techContent.component).toEqual(ExperienceItemTechComponent)
expect(techContent.inputs).toEqual({
technologies,
projectNames: projects.map((project) => project.name),
} satisfies Partial<ExperienceItemTechComponent>)
})
})
})

const makeSut = () => experienceItemToContents
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ import { ChippedContent } from '../../chipped-content/chipped-content'
import { isNotUndefined } from '@/common/is-not-undefined'
import { TextContentComponent } from '../../chipped-content/text-content/text-content.component'
import { ExperienceItemHighlightsComponent } from './experience-item-highlights/experience-item-highlights.component'
import { ExperienceItemTechComponent } from './experience-item-tech/experience-item-tech.component'
import { TechnologyItem } from '../../technology/technology-item'

type ExperienceItemToContents = (
item: ExperienceItem,
) => ReadonlyArray<ChippedContent>
export const experienceItemToContents: ExperienceItemToContents = (item) =>
[
item.summary
? new ChippedContent({
displayName: 'Summary',
component: TextContentComponent,
inputs: {
text: item.summary,
},
})
: undefined,
export const experienceItemToContents: ExperienceItemToContents = (item) => {
const summaryContent = item.summary
? new ChippedContent({
displayName: 'Summary',
component: TextContentComponent,
inputs: {
text: item.summary,
},
})
: undefined
const highlightsContent =
item.highlights.length > 0
? new ChippedContent({
displayName: 'Highlights',
Expand All @@ -26,5 +28,24 @@ export const experienceItemToContents: ExperienceItemToContents = (item) =>
highlights: item.highlights,
},
})
: undefined,
].filter(isNotUndefined)
: undefined
const projectsTechnologies = item.projects.flatMap(
(project) => project.technologies,
)
const techContent =
projectsTechnologies.length > 0
? new ChippedContent({
displayName: 'Tech',
component: ExperienceItemTechComponent,
inputs: {
technologies: Array.from(
new Set(projectsTechnologies.map<string>(({ slug }) => slug)),
).map<TechnologyItem>((slug) => ({ slug })),
projectNames: item.projects
.filter((project) => project.technologies.length > 0)
.map((project) => project.name),
},
})
: undefined
return [summaryContent, highlightsContent, techContent].filter(isNotUndefined)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Organization } from '../../organization'
import { DateRange } from '../../date-range/date-range'
import { ProjectItem } from '../../projects-section/project-item/project-item'

export class ExperienceItem {
public readonly company: Organization
Expand All @@ -11,6 +12,7 @@ export class ExperienceItem {
public readonly internship: boolean
public readonly promotions: boolean
public readonly morePositions: boolean
public readonly projects: ReadonlyArray<ProjectItem>

constructor({
company,
Expand All @@ -22,6 +24,7 @@ export class ExperienceItem {
internship,
promotions,
morePositions,
projects,
}: {
company: Organization
position: string
Expand All @@ -32,6 +35,7 @@ export class ExperienceItem {
internship?: boolean
promotions?: boolean
morePositions?: boolean
projects?: ReadonlyArray<ProjectItem>
}) {
this.company = company
this.position = position
Expand All @@ -42,5 +46,6 @@ export class ExperienceItem {
this.internship = internship ?? false
this.promotions = promotions ?? false
this.morePositions = morePositions ?? false
this.projects = projects ?? []
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JsonResumeProject } from '../adapt-json-resume-project'
import resume from '../../../../../assets/resume.json'
import { JsonResumeProject } from '../json-resume-projects'

const sampleJsonResumeProject = resume.projects[0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ADAPT_JSON_RESUME_PROJECT,
AdaptJsonResumeProject,
InvalidStackValueError,
JsonResumeProject,
} from './adapt-json-resume-project'
import { MockProvider } from 'ng-mocks'
import { Stack } from './project-item/project-item'
Expand All @@ -12,6 +11,7 @@ import {
RelativizeProductionUrl,
} from '@/common/relativize-production-url'
import { makeJsonResumeProject } from './__tests__/make-json-resume-project'
import { JsonResumeProject } from './json-resume-projects'

describe('AdaptJsonResumeProject', () => {
it('should be created', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { inject, InjectionToken } from '@angular/core'
import resume from '../../../../assets/resume.json'
import { ProjectItem, Stack } from './project-item/project-item'
import { DateRange } from '../date-range/date-range'
import { RELATIVIZE_PRODUCTION_URL } from '@/common/relativize-production-url'
import { JsonResumeProject } from './json-resume-projects'

export type AdaptJsonResumeProject = (project: JsonResumeProject) => ProjectItem
export const ADAPT_JSON_RESUME_PROJECT =
Expand Down Expand Up @@ -42,5 +42,3 @@ export class InvalidStackValueError extends Error {
super(`Invalid stack value: '${value}'`)
}
}

export type JsonResumeProject = (typeof resume.projects)[number]
13 changes: 6 additions & 7 deletions src/app/resume-page/projects-section/get-project-items.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import {
GET_PROJECT_ITEMS,
GetProjectItems,
JSON_RESUME_PROJECTS,
JsonResumeProjects,
} from './get-project-items'
import { GET_PROJECT_ITEMS, GetProjectItems } from './get-project-items'
import { MockProvider } from 'ng-mocks'
import {
ADAPT_JSON_RESUME_PROJECT,
AdaptJsonResumeProject,
JsonResumeProject,
} from './adapt-json-resume-project'
import { ProjectItem } from './project-item/project-item'
import { serviceTestSetup } from '@/test/helpers/service-test-setup'
import {
JSON_RESUME_PROJECTS,
JsonResumeProject,
JsonResumeProjects,
} from './json-resume-projects'

describe('GetProjectItems', () => {
it('should be created', () => {
Expand Down
Loading

0 comments on commit 8dcd750

Please sign in to comment.