Skip to content

Commit 45a836e

Browse files
authored
Add some loading wrapper for entities (#21)
1 parent 39c96f0 commit 45a836e

26 files changed

+904
-297
lines changed

frontend/src/app/app.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { TestBed } from '@angular/core/testing';
22
import { AppComponent } from './app.component';
3-
import { RouterTestingModule } from "@angular/router/testing";
43
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
4+
import { RouterModule } from "@angular/router";
55

66
describe('AppComponent', () => {
77
beforeEach(async () => {
88
await TestBed.configureTestingModule({
99
imports: [
10-
RouterTestingModule
10+
RouterModule.forRoot([])
1111
],
1212
declarations: [
1313
AppComponent

frontend/src/app/common/guards/authentication.guard.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { AuthenticationService } from "../services/utils/authentication.service"
55
export const authenticationGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
66
const authService = inject(AuthenticationService);
77
const router = inject(Router);
8+
9+
// If user is authenticated, return true, otherwise redirect to login page with returnUrl query parameter.
810
if (authService.currentUserToken) {
911
return true;
1012
} else {
Lines changed: 114 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,114 @@
1-
// import { TestBed } from '@angular/core/testing';
2-
//
3-
// import { ArticleService } from './article.service';
4-
//
5-
// describe('ArticleService', () => {
6-
// let service: ArticleService;
7-
//
8-
// beforeEach(() => {
9-
// TestBed.configureTestingModule({});
10-
// service = TestBed.inject(ArticleService);
11-
// });
12-
//
13-
// it('should be created', () => {
14-
// // expect(service).toBeTruthy();
15-
// });
16-
// });
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { ArticleService } from './article.service';
4+
import { RequestHelperService } from "../utils/request-helper.service";
5+
import { of } from "rxjs";
6+
import { CreateArticlePayload } from "../../models/api/article.model";
7+
8+
describe('ArticleService', () => {
9+
let service: ArticleService;
10+
let spyRequestHelperService: Partial<jasmine.SpyObj<RequestHelperService>>;
11+
12+
beforeEach(()=> {
13+
spyRequestHelperService = {
14+
get: jasmine.createSpy('get'),
15+
post: jasmine.createSpy('post'),
16+
put: jasmine.createSpy('put'),
17+
delete: jasmine.createSpy('delete')
18+
}
19+
spyRequestHelperService.get!.and.returnValue(of(null));
20+
spyRequestHelperService.post!.and.returnValue(of(null));
21+
spyRequestHelperService.put!.and.returnValue(of(null));
22+
spyRequestHelperService.delete!.and.returnValue(of(null));
23+
});
24+
25+
beforeEach(() => {
26+
TestBed.configureTestingModule({
27+
providers: [
28+
ArticleService,
29+
{ provide: RequestHelperService, useValue: spyRequestHelperService }
30+
]
31+
});
32+
service = TestBed.inject(ArticleService);
33+
});
34+
35+
it('should be created', () => {
36+
expect(service).toBeTruthy();
37+
});
38+
39+
it('should create article', ()=> {
40+
const expectedPayload: CreateArticlePayload = {
41+
article: {
42+
title: 'title',
43+
description: 'description',
44+
body: 'body',
45+
tagList: ['tag1', 'tag2']
46+
}
47+
}
48+
service.createArticle(expectedPayload);
49+
expect(spyRequestHelperService.post).toHaveBeenCalledWith('/articles', expectedPayload);
50+
});
51+
52+
it('should get article', ()=> {
53+
const expectedSlug = 'slug';
54+
service.getArticle(expectedSlug);
55+
expect(spyRequestHelperService.get).toHaveBeenCalledWith(`/articles/${expectedSlug}`);
56+
});
57+
58+
it('should update article', ()=> {
59+
const expectedSlug = 'slug';
60+
const expectedPayload = { article: { title: 'title' } };
61+
service.updateArticle(expectedSlug, expectedPayload);
62+
expect(spyRequestHelperService.put).toHaveBeenCalledWith(`/articles/${expectedSlug}`, expectedPayload);
63+
});
64+
65+
it('should delete article', ()=> {
66+
const expectedSlug = 'slug';
67+
service.deleteArticle(expectedSlug);
68+
expect(spyRequestHelperService.delete).toHaveBeenCalledWith(`/articles/${expectedSlug}`);
69+
});
70+
71+
it('should query articles', ()=> {
72+
const expectedParams = { tag: 'tag' };
73+
service.queryArticles(expectedParams);
74+
expect(spyRequestHelperService.get).toHaveBeenCalledWith('/articles', {params: expectedParams});
75+
});
76+
77+
it('should query feed articles', ()=> {
78+
const expectedParams = { tag: 'tag' };
79+
service.queryFeedArticles(expectedParams);
80+
expect(spyRequestHelperService.get).toHaveBeenCalledWith('/articles/feed', {params: expectedParams});
81+
});
82+
83+
it('should favorite article', ()=> {
84+
const expectedSlug = 'slug';
85+
service.favoriteArticle(expectedSlug);
86+
expect(spyRequestHelperService.post).toHaveBeenCalledWith(`/articles/${expectedSlug}/favorite`, null);
87+
});
88+
89+
it('should unfavorite article', ()=> {
90+
const expectedSlug = 'slug';
91+
service.unfavoriteArticle(expectedSlug);
92+
expect(spyRequestHelperService.delete).toHaveBeenCalledWith(`/articles/${expectedSlug}/favorite`);
93+
});
94+
95+
it('should query article comments', ()=> {
96+
const expectedSlug = 'slug';
97+
service.queryArticleComments(expectedSlug);
98+
expect(spyRequestHelperService.get).toHaveBeenCalledWith(`/articles/${expectedSlug}/comments`);
99+
});
100+
101+
it('should create article comment', ()=> {
102+
const expectedSlug = 'slug';
103+
const expectedPayload = { comment: { body: 'body' } };
104+
service.createArticleComment(expectedSlug, expectedPayload);
105+
expect(spyRequestHelperService.post).toHaveBeenCalledWith(`/articles/${expectedSlug}/comments`, expectedPayload);
106+
});
107+
108+
it('should delete article comment', ()=> {
109+
const expectedSlug = 'slug';
110+
const expectedCommentId = 2;
111+
service.deleteArticleComment(expectedSlug, expectedCommentId);
112+
expect(spyRequestHelperService.delete).toHaveBeenCalledWith(`/articles/${expectedSlug}/comments/${expectedCommentId}`);
113+
});
114+
});

frontend/src/app/common/services/api/article.service.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
ArticleCommentsResponse,
1414
CreateArticleCommentPayload
1515
} from "../../models/api/comment.model";
16-
import { TagsResponse } from "../../models/api/tag.model";
1716

1817
@Injectable({
1918
providedIn: 'root'
@@ -67,8 +66,4 @@ export class ArticleService {
6766
public deleteArticleComment(articleSlug: string, commentId: number): Observable<void> {
6867
return this._requestHelper.delete(`/articles/${ articleSlug }/comments/${ commentId }`);
6968
}
70-
71-
public queryTags(): Observable<TagsResponse> {
72-
return this._requestHelper.get('/tags');
73-
}
7469
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { TagService } from './tag.service';
4+
import { RequestHelperService } from "../utils/request-helper.service";
5+
import { of } from "rxjs";
6+
7+
describe('TagService', () => {
8+
let service: TagService;
9+
10+
let spyRequestHelperService: Partial<jasmine.SpyObj<RequestHelperService>>;
11+
12+
beforeEach(()=> {
13+
spyRequestHelperService = {
14+
get: jasmine.createSpy('get')
15+
}
16+
spyRequestHelperService.get!.and.returnValue(of(null));
17+
});
18+
19+
beforeEach(() => {
20+
TestBed.configureTestingModule({
21+
providers: [
22+
TagService,
23+
{ provide: RequestHelperService, useValue: spyRequestHelperService }
24+
]
25+
});
26+
service = TestBed.inject(TagService);
27+
});
28+
29+
it('should be created', () => {
30+
expect(service).toBeTruthy();
31+
});
32+
33+
it('should query tags', ()=> {
34+
service.query();
35+
expect(spyRequestHelperService.get).toHaveBeenCalledWith('/tags');
36+
});
37+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Injectable } from '@angular/core';
2+
import { RequestHelperService } from "../utils/request-helper.service";
3+
import { Observable } from "rxjs";
4+
import { TagsResponse } from "../../models/api/tag.model";
5+
6+
@Injectable({
7+
providedIn: 'root'
8+
})
9+
export class TagService {
10+
11+
constructor(
12+
private readonly _requestHelper: RequestHelperService
13+
) { }
14+
15+
public query(): Observable<TagsResponse> {
16+
return this._requestHelper.get('/tags');
17+
}
18+
}

frontend/src/app/layout/footer/footer.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<footer>
22
<div class="container">
3-
<a href="/" class="logo-font">conduit</a>
3+
<a routerLink="/" class="logo-font">conduit</a>
44
<span class="attribution">
55
An interactive learning project from <a href="https://thinkster.io">Thinkster</a>. Code &amp;
66
design licensed under MIT. Implemented by <a href="https://github.com/anhptk">&#64;anhptk</a> and <a href="https://github.com/thanhdev">&#64;thanhdev</a>

frontend/src/app/layout/footer/footer.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Component } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
22

33
@Component({
44
selector: 'app-footer',
55
templateUrl: './footer.component.html',
6-
styleUrl: './footer.component.scss'
6+
styleUrl: './footer.component.scss',
7+
changeDetection: ChangeDetectionStrategy.OnPush
78
})
89
export class FooterComponent {
910

frontend/src/app/layout/header/header.component.spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
22

33
import { HeaderComponent } from './header.component';
44
import { AuthenticationService } from "../../common/services/utils/authentication.service";
5-
import { RouterTestingModule } from "@angular/router/testing";
65
import { UserService } from "../../common/services/api/user.service";
76
import { of } from "rxjs";
87
import { User } from "../../common/models/api/user.model";
8+
import { RouterModule } from "@angular/router";
99

1010
describe('HeaderComponent', () => {
1111
let component: HeaderComponent;
@@ -37,7 +37,7 @@ describe('HeaderComponent', () => {
3737
await TestBed.configureTestingModule({
3838
declarations: [HeaderComponent],
3939
imports: [
40-
RouterTestingModule
40+
RouterModule.forRoot([])
4141
],
4242
providers: [
4343
{provide: AuthenticationService, useValue: spyAuthenticationService},
@@ -58,4 +58,12 @@ describe('HeaderComponent', () => {
5858
it('should load current user', () => {
5959
expect(spyUserService.getCurrentUser).toHaveBeenCalledTimes(1);
6060
});
61+
62+
it('should display menu items for logged in user', () => {
63+
expect(component.menuItems.length).toBe(4);
64+
expect(component.menuItems[0].name).toBe('Home');
65+
expect(component.menuItems[1].name).toBe('New Article');
66+
expect(component.menuItems[2].name).toBe('Settings');
67+
expect(component.menuItems[3].name).toBe('Profile');
68+
});
6169
});
Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,65 @@
1-
// import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
//
3-
// import { ArticleCommentsComponent } from './article-comments.component';
4-
//
5-
// describe('ArticleCommentsComponent', () => {
6-
// let component: ArticleCommentsComponent;
7-
// let fixture: ComponentFixture<ArticleCommentsComponent>;
8-
//
9-
// beforeEach(async () => {
10-
// await TestBed.configureTestingModule({
11-
// declarations: [ArticleCommentsComponent]
12-
// })
13-
// .compileComponents();
14-
//
15-
// fixture = TestBed.createComponent(ArticleCommentsComponent);
16-
// component = fixture.componentInstance;
17-
// fixture.detectChanges();
18-
// });
19-
//
20-
// it('should create', () => {
21-
// expect(component).toBeTruthy();
22-
// });
23-
// });
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { ArticleCommentsComponent } from './article-comments.component';
4+
import { RouterModule } from "@angular/router";
5+
import { AuthenticationService } from "../../../common/services/utils/authentication.service";
6+
import { ArticleService } from "../../../common/services/api/article.service";
7+
import { of } from "rxjs";
8+
import { ArticleComment } from "../../../common/models/api/comment.model";
9+
10+
describe('ArticleCommentsComponent', () => {
11+
let component: ArticleCommentsComponent;
12+
let fixture: ComponentFixture<ArticleCommentsComponent>;
13+
14+
let spyArticleService: Partial<jasmine.SpyObj<ArticleService>>;
15+
16+
beforeEach(() => {
17+
spyArticleService = {
18+
queryArticleComments: jasmine.createSpy('queryArticleComments'),
19+
createArticleComment: jasmine.createSpy('createArticleComment'),
20+
deleteArticleComment: jasmine.createSpy('deleteArticleComment')
21+
}
22+
spyArticleService.queryArticleComments?.and.returnValue(of({comments: [], commentsCount: 0}));
23+
spyArticleService.createArticleComment?.and.returnValue(of({comment: {} as ArticleComment}));
24+
spyArticleService.deleteArticleComment?.and.returnValue(of(void 0));
25+
})
26+
27+
beforeEach(async () => {
28+
await TestBed.configureTestingModule({
29+
declarations: [ArticleCommentsComponent],
30+
imports: [
31+
RouterModule.forRoot([
32+
{ path: 'article/1', component: ArticleCommentsComponent },
33+
])
34+
],
35+
providers: [
36+
AuthenticationService,
37+
{ provide: ArticleService, useValue: spyArticleService }
38+
]
39+
})
40+
.compileComponents();
41+
42+
fixture = TestBed.createComponent(ArticleCommentsComponent);
43+
component = fixture.componentInstance;
44+
fixture.detectChanges();
45+
});
46+
47+
it('should create', () => {
48+
expect(component).toBeTruthy();
49+
});
50+
51+
it('should load comments', () => {
52+
expect(spyArticleService.queryArticleComments).toHaveBeenCalledTimes(1);
53+
});
54+
55+
it('should add comment', () => {
56+
component.newCommentControl.setValue('test');
57+
component.addComment();
58+
expect(spyArticleService.createArticleComment).toHaveBeenCalledTimes(1);
59+
});
60+
61+
it('should delete comment', () => {
62+
component.deleteComment(1);
63+
expect(spyArticleService.deleteArticleComment).toHaveBeenCalledTimes(1);
64+
});
65+
});

0 commit comments

Comments
 (0)