Skip to content

Commit

Permalink
chore: Add article mocks; feat: Tighten up Modification Info typing s…
Browse files Browse the repository at this point in the history
…ince it should always be defined; add Article component specs
  • Loading branch information
mwiraszka committed Jan 3, 2025
1 parent 6efdace commit 31af014
Show file tree
Hide file tree
Showing 28 changed files with 500 additions and 85 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"globals": "^15.14.0",
"jest": "29.7.0",
"jest-preset-angular": "14.4.2",
"ng-mocks": "^14.13.1",
"prettier": "3.3.3",
"prettier-eslint": "16.3.0",
"ts-node": "^10.9.2",
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/app/components/article-grid/article-grid.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ <h3 class="lcc-truncate-max-2-lines">{{ article.title }}</h3>
</p>
<div class="article-dates-container">
<span class="date-created">
{{ article.modificationInfo?.dateCreated | formatDate: 'short no-time' }}
{{ article.modificationInfo.dateCreated | formatDate: 'short no-time' }}
</span>
@if (article.modificationInfo | wasEdited) {
<div>
<span class="vertical-spacer">|</span>
<span class="edited">edited&nbsp;</span>
<span class="date-last-edited">
{{
article.modificationInfo?.dateLastEdited | formatDate: 'short no-time'
article.modificationInfo.dateLastEdited | formatDate: 'short no-time'
}}
</span>
</div>
Expand Down
38 changes: 22 additions & 16 deletions src/app/components/article/article.component.html
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
<div class="banner-image-container">
@defer {
<img
[src]="article?.imageUrl"
class="banner-image"
[src]="article!.imageUrl"
default="assets/image-placeholder.png"
alt="Article image banner" />
} @placeholder (minimum 0.5s) {
<div class="lcc-content-placeholder-wrapper loading-placeholder-image">
<div class="lcc-content-placeholder"></div>
</div>
}
<h2 class="lcc-truncate-max-2-lines">
{{ article?.title | truncateByChars: 120 }}
</h2>

@if (article | isDefined) {
<h2 class="title lcc-truncate-max-2-lines">
{{ article.title | truncateByChars: 120 }}
</h2>
}
</div>

<div class="article-details-container h4">
<div class="author-container">
<span class="author-name">
{{ article?.modificationInfo?.createdBy }}
</span>
<span class="date-created">
{{ article?.modificationInfo?.dateCreated | formatDate: 'short' }}
</span>
@if (article | isDefined) {
<span class="author-name">
{{ article.modificationInfo.createdBy }}
</span>
<span class="date-created">
{{ article.modificationInfo.dateCreated | formatDate: 'short' }}
</span>
}
</div>

@if (article?.modificationInfo | wasEdited) {
<div class="editor-container">
<div class="editor-container">
@if ((article | isDefined) && (article.modificationInfo | wasEdited)) {
<span class="updated">last updated</span>
<span class="editor-name">
{{ article?.modificationInfo?.lastEditedBy }}
{{ article.modificationInfo.lastEditedBy }}
</span>
<span class="vertical-spacer">|</span>
<span class="date-last-edited">
{{ article?.modificationInfo?.dateLastEdited | formatDate: 'short' }}
{{ article.modificationInfo.dateLastEdited | formatDate: 'short' }}
</span>
</div>
}
}
</div>
</div>

<hr />
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/article/article.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}
}

h2 {
.title {
position: absolute;
bottom: 8px;
left: 16px;
Expand Down
165 changes: 165 additions & 0 deletions src/app/components/article/article.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { MockComponent } from 'ng-mocks';

import { DebugElement } from '@angular/core';
import { ComponentFixture, DeferBlockState, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { MarkdownRendererComponent } from '@app/components/markdown-renderer/markdown-renderer.component';
import { MOCK_ARTICLES } from '@app/mocks/articles.mock';

import { ArticleComponent } from './article.component';

describe('ArticleComponent', () => {
let fixture: ComponentFixture<ArticleComponent>;
let component: ArticleComponent;

beforeEach(() => {
TestBed.overrideComponent(ArticleComponent, {
remove: { imports: [MarkdownRendererComponent] },
add: { imports: [MockComponent(MarkdownRendererComponent)] },
});

TestBed.configureTestingModule({
imports: [ArticleComponent],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(ArticleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

describe('without article', () => {
beforeEach(async () => {
component.article = undefined;
fixture.detectChanges();
});

it('should render all containers', () => {
expect(getDebugElement('.banner-image-container')).not.toBeNull();
expect(getDebugElement('.article-details-container')).not.toBeNull();
expect(getDebugElement('.editor-container')).not.toBeNull();
expect(getDebugElement('.markdown-container')).not.toBeNull();
});

it('should render loading placeholder image', () => {
expect(getDebugElement('.loading-placeholder-image')).not.toBeNull();
});

it('should render Markdown Renderer component', () => {
expect(getDebugElement('lcc-markdown-renderer')).not.toBeNull();
});

it('should not render any article content elements', () => {
expect(getDebugElement('.banner-image')).toBeNull();
expect(getDebugElement('.title')).toBeNull();
expect(getDebugElement('.author-name')).toBeNull();
expect(getDebugElement('.date-created')).toBeNull();
expect(getDebugElement('.updated')).toBeNull();
expect(getDebugElement('.editor-name')).toBeNull();
expect(getDebugElement('.date-last-edited')).toBeNull();
});
});

describe('with article', () => {
beforeEach(() => {
component.article = MOCK_ARTICLES[2];
fixture.detectChanges();
});

it('should render all containers', () => {
expect(getDebugElement('.banner-image-container')).not.toBeNull();
expect(getDebugElement('.article-details-container')).not.toBeNull();
expect(getDebugElement('.editor-container')).not.toBeNull();
expect(getDebugElement('.markdown-container')).not.toBeNull();
});

it('should render all article content elements and Markdown Renderer component', () => {
expect(getDebugElement('.title')).not.toBeNull();
expect(getDebugElement('.author-name')).not.toBeNull();
expect(getDebugElement('.date-created')).not.toBeNull();
expect(getDebugElement('.updated')).not.toBeNull();
expect(getDebugElement('.editor-name')).not.toBeNull();
expect(getDebugElement('.date-last-edited')).not.toBeNull();
expect(getDebugElement('lcc-markdown-renderer')).not.toBeNull();
});

it('should initially render loading placeholder image', () => {
expect(getDebugElement('.loading-placeholder-image')).not.toBeNull();
expect(getDebugElement('img')).toBeNull();
});

// Currently not possible to retrieve deferrable blocks due to ng-mocks bug:
// /~https://github.com/help-me-mom/ng-mocks/issues/7742
xit('should replace placeholder with image once finished loading', async () => {
const deferBlocks = await fixture.getDeferBlocks();
deferBlocks[0]?.render(DeferBlockState.Complete);

expect(getDebugElement('.loading-placeholder-image')).toBeNull();
expect(getDebugElement('img')).not.toBeNull();
});

it('should truncate article title to 120 characters', () => {
expect(getDebugElement('.title')?.nativeElement.textContent.trim()).toHaveLength(
120,
);
});

it("should include the article author's and editor's names", () => {
expect(getDebugElement('.author-name')?.nativeElement.textContent.trim()).toBe(
MOCK_ARTICLES[2].modificationInfo.createdBy,
);

expect(getDebugElement('.editor-name')?.nativeElement.textContent.trim()).toBe(
MOCK_ARTICLES[2].modificationInfo.lastEditedBy,
);
});

it('should format dates correctly', () => {
expect(getDebugElement('.date-created')?.nativeElement.textContent.trim()).toBe(
'Wed, Jan 1, 2025, 12:00 PM',
);

expect(getDebugElement('.date-last-edited')?.nativeElement.textContent.trim()).toBe(
'Thu, Jan 2, 2025, 10:00 AM',
);
});

describe('when article creation and last edited date are on the same day', () => {
beforeEach(() => {
component.article = MOCK_ARTICLES[4];
fixture.detectChanges();
});

it('should display article creation info but not last edit info', () => {
expect(getDebugElement('.date-created')?.nativeElement.textContent.trim()).toBe(
'Mon, Jan 20, 2025, 2:00 PM',
);
expect(getDebugElement('.date-last-edited')).toBeNull();
});
});

describe('when article image URL is not available', () => {
beforeEach(() => {
component.article = MOCK_ARTICLES[3];
fixture.detectChanges();
});

it('should keep displaying loading placeholder image', async () => {
expect(getDebugElement('.banner-image')).toBeNull();
expect(getDebugElement('.loading-placeholder-image')).not.toBeNull();

const deferBlocks = await fixture.getDeferBlocks();
deferBlocks[0]?.render(DeferBlockState.Complete);

expect(getDebugElement('.banner-image')).toBeNull();
expect(getDebugElement('.loading-placeholder-image')).not.toBeNull();
});
});
});

function getDebugElement(selector: string): DebugElement | null {
return fixture.debugElement.query(By.css(selector));
}
});
8 changes: 7 additions & 1 deletion src/app/components/article/article.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { Component, Input } from '@angular/core';

import { ImagePreloadDirective } from '@app/components/image-preload/image-preload.directive';
import { MarkdownRendererComponent } from '@app/components/markdown-renderer/markdown-renderer.component';
import { FormatDatePipe, TruncateByCharsPipe, WasEditedPipe } from '@app/pipes';
import {
FormatDatePipe,
IsDefinedPipe,
TruncateByCharsPipe,
WasEditedPipe,
} from '@app/pipes';
import type { Article } from '@app/types';

@Component({
Expand All @@ -14,6 +19,7 @@ import type { Article } from '@app/types';
CommonModule,
FormatDatePipe,
ImagePreloadDirective,
IsDefinedPipe,
MarkdownRendererComponent,
TruncateByCharsPipe,
WasEditedPipe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
{{ member?.lichessUsername }}
</td>
<td class="last-updated">
{{ member.modificationInfo!.dateLastEdited | formatDate: 'short no-time' }}
{{ member.modificationInfo.dateLastEdited | formatDate: 'short no-time' }}
</td>
@if (vm.isAdmin) {
<td class="born">{{ member?.yearOfBirth }}</td>
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/schedule/schedule.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ <h4 class="title">{{ event.title }}</h4>
<aside class="created-and-edited">
<div>
Event created
{{ event.modificationInfo?.dateCreated | formatDate: 'short no-time' }}
{{ event.modificationInfo.dateCreated | formatDate: 'short no-time' }}
</div>
<div>
Last edited
{{
event.modificationInfo?.dateLastEdited | formatDate: 'short no-time'
event.modificationInfo.dateLastEdited | formatDate: 'short no-time'
}}
</div>
</aside>
Expand Down
Loading

0 comments on commit 31af014

Please sign in to comment.