Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions FE/angular/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MovieComponent } from './components/movie/movie.component';
import { MoviesComponent } from './components/movies/movies.component';

const routes: Routes = [
{
path: '',
component: MoviesComponent
loadChildren: () => import('./components/movies/movies.module').then((m) => m.MoviesModule)
},
{
path: 'movie/:id',
component: MovieComponent
}
{ path: 'movie/:id', loadChildren: () => import('./components/movie/movie.module').then((m) => m.MovieModule) }
];

@NgModule({
Expand Down
1 change: 0 additions & 1 deletion FE/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<div id="container">
<div id="content">
<h1>{{ pageTitle }}</h1>
<router-outlet></router-outlet>
</div>
</div>
4 changes: 2 additions & 2 deletions FE/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import { Component } from '@angular/core';
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
public pageTitle = 'Movies';
export class AppComponent{

}
25 changes: 5 additions & 20 deletions FE/angular/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,14 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MovieComponent } from './components/movie/movie.component';
import { MoviesComponent } from './components/movies/movies.component';
import { DecadesComponent } from './components/navigation/decades/decades.component';
import { GoBackComponent } from './components/navigation/go-back/go-back.component';
import { GoDetailsComponent } from './components/navigation/go-details/go-details.component';
import { GoImdbComponent } from './components/navigation/go-imdb/go-imdb.component';
import { NavigationService } from './components/navigation/navigation.service';
import { SidebarComponent } from './components/sidebar/sidebar.component';
import { DataService } from './services/data.service';
import { MovieModule } from './components/movie/movie.module';
import { MoviesModule } from './components/movies/movies.module';

@NgModule({
declarations: [
MoviesComponent,
MovieComponent,
SidebarComponent,
GoBackComponent,
GoDetailsComponent,
GoImdbComponent,
AppComponent,
DecadesComponent
],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [DataService, NavigationService],
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule, MovieModule, MoviesModule],
providers: [NavigationService],
bootstrap: [AppComponent]
})
export class AppModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="movie-detail">
<div class="image col-3"><img [src]="movie.Poster" (error)="handleImageError($event)"/></div>
<div class="content col-9 col-grid">
<div class="title">{{ movie.Title }} <span *ngIf="!isMovies"> {{movie.Year}} </span></div>
<ul class="metadata">
<li class="rated" [attr.aria-label]="movie.Rated">
<div>{{ movie.Rated }}</div>
</li>
<li class="runtime" [attr.aria-label]="movie.Runtime">{{ movie.Runtime }}</li>
<li class="genre" *ngIf="!isMovies" [attr.aria-label]="movie.Genre">{{ movie.Genre }}</li>
<li class="release" [attr.aria-label]="movie.Released">{{ movie.Released }}</li>
</ul>
<ul class="details" *ngIf="!isMovies">
<li [attr.aria-label]="movie.Director">
<strong>Director:</strong>
{{ movie.Director }}
</li>
<li [attr.aria-label]="movie.Writer">
<strong>Writer:</strong>
{{ movie.Writer }}
</li>
<li [attr.aria-label]="movie.Actors">
<strong>Stars:</strong>
{{ movie.Actors }}
</li>
</ul>
<div class="plot" [attr.aria-label]="movie.Plot">
{{ movie.Plot }}
</div>
<div class="nav flex-footer">
<app-go-details *ngIf="isMovies" [movieName]="movie.Title" [imdbId]="movie.imdbID"></app-go-details>
<app-go-imdb *ngIf="!isMovies" [imdbId]="movie.imdbID"></app-go-imdb>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.movie-detail {
display: flex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Component, Input } from '@angular/core';
import { MovieComplete } from '../movie/movie.models';

@Component({
selector: 'movie-detail',
templateUrl: './movie-detail.component.html',
styleUrls: ['./movie-detail.component.scss']
})
export class MovieDetailComponent {
@Input() isMovies = false;

@Input() movie: MovieComplete;

public fallBackImage: string = './assets/images/placeholder.jpg';

handleImageError(event: Event): void {
(event.target as HTMLImageElement).src = this.fallBackImage
}
}
12 changes: 12 additions & 0 deletions FE/angular/src/app/components/movie-detail/movie-detail.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MovieDetailComponent } from './movie-detail.component';
import { GoImdbModule } from '../navigation/go-imdb/go-imdb.module';
import { GoDetailsModule } from '../navigation/go-details/go-details.module';

@NgModule({
declarations: [MovieDetailComponent],
imports: [CommonModule, GoImdbModule, GoDetailsModule],
exports: [MovieDetailComponent]
})
export class MovieDetailModule {}
37 changes: 37 additions & 0 deletions FE/angular/src/app/components/movie/data.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { HttpClient } from '@angular/common/http';
import { mockProvider, SpectatorService } from '@ngneat/spectator';
import { createServiceFactory } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { MovieDataService } from './data.service';
import { mockMovies } from '../../../app/tests/mock-data';
import { Constants } from '../../../app/constants/constant';

const mockGet = jest.fn().mockReturnValue(of([]));
const mockHttpClient = mockProvider(HttpClient, {
get: mockGet
});

describe('MovieDataService', () => {
let spectator: SpectatorService<MovieDataService>;
let service: MovieDataService;
const createService = createServiceFactory({
service: MovieDataService,
imports: [],
declarations: [],
providers: [mockHttpClient]
});

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
spectator = createService();
service = spectator.service;
});

test('http method should return the desired result', () => {
expect(service).toBeTruthy();
mockGet.mockReturnValueOnce(of(mockMovies[1]));
service.getMovie(mockMovies[1].imdbID);
expect(mockGet).toBeCalledWith(`${Constants.serviceUrl}i=${mockMovies[1].imdbID}`);
});
});
30 changes: 30 additions & 0 deletions FE/angular/src/app/components/movie/data.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { Constants } from '../../constants/constant';
import { HttpClient } from '@angular/common/http';
import { MovieComplete, MovieDetails } from './movie.models';

@Injectable()
export class MovieDataService {
constructor(private http: HttpClient) {}

public getMovie(id: string): Observable<MovieComplete> {
return this.http.get<MovieDetails>(`${Constants.serviceUrl}i=${id}`).pipe(
map(({ Actors, Director, Genre, imdbID, Plot, Poster, Rated, Released, Runtime, Title, Type, Writer, Year }) => ({
Actors,
Director,
Genre,
imdbID,
Plot,
Poster: Poster.replace(Constants.posterUrl, Constants.replacePosterUrl),
Rated,
Released,
Runtime,
Title,
Type,
Writer,
Year: parseInt(Year as string)
}))
);
}
}
11 changes: 11 additions & 0 deletions FE/angular/src/app/components/movie/movie-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MovieComponent } from './movie.component';

const routes: Routes = [{ path: '', component: MovieComponent }];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MovieRoutingModule {}
35 changes: 2 additions & 33 deletions FE/angular/src/app/components/movie/movie.component.html
Original file line number Diff line number Diff line change
@@ -1,38 +1,7 @@
<app-go-back></app-go-back>

<ul *ngIf="movie" class="movie">
<ul *ngIf="movie$ | async as movie" class="movie">
<li class="grid grid-bleed">
<div class="image col-3"><img [src]="movie.Poster" /></div>
<div class="content col-9 col-grid">
<div class="title">{{ movie.Title }} ({{ movie.Year }})</div>
<ul class="metadata">
<li class="rated">
<div>{{ movie.Rated }}</div>
</li>
<li class="runtime">{{ movie.Runtime }}</li>
<li class="genre">{{ movie.Genre }}</li>
<li class="release">{{ movie.Released }}</li>
</ul>
<ul class="details">
<li>
<strong>Director:</strong>
{{ movie.Director }}
</li>
<li>
<strong>Writer:</strong>
{{ movie.Writer }}
</li>
<li>
<strong>Stars:</strong>
{{ movie.Actors }}
</li>
</ul>
<div class="plot">
{{ movie.Plot }}
</div>
<div class="nav flex-footer">
<app-go-imdb [imdbId]="movie.imdbID"></app-go-imdb>
</div>
</div>
<movie-detail [isMovies]="false" [movie]="movie"></movie-detail>
</li>
</ul>
20 changes: 15 additions & 5 deletions FE/angular/src/app/components/movie/movie.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { ActivatedRoute } from '@angular/router';
import { mockProvider, Spectator } from '@ngneat/spectator';
import { createComponentFactory } from '@ngneat/spectator/jest';
import { DataService } from '../../services/data.service';
import { MovieDataService } from './data.service';
import { MovieComponent } from './movie.component';
import { of } from 'rxjs';
import { mockMovies } from '../../tests/mock-data';

const mockActivatedRoute = mockProvider(ActivatedRoute, {
params: jest.fn()
params: of(({id: 'tt123'}))
});
const mockDataService = mockProvider(DataService, {
getMovie: jest.fn()
const mockDataService = mockProvider(MovieDataService, {
getMovie: jest.fn().mockReturnValue(of({ ...mockMovies[0] }))
});

describe('MovieComponent', () => {
Expand All @@ -26,10 +28,18 @@ describe('MovieComponent', () => {
beforeEach(() => {
spectator = createComponent();
component = spectator.component;
component.ngOnInit();
});

test('should create the component', () => {
component.ngOnInit();
expect(component).toBeTruthy();
});

test('should have the expected movie id', () => {
expect(component.movieId).toBe('tt123')
})

test('should not have the following movie id', () => {
expect(component.movieId).not.toBe('tt124')
})
});
33 changes: 21 additions & 12 deletions FE/angular/src/app/components/movie/movie.component.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { tap } from 'rxjs';
import { DataService, MovieComplete } from '../../services/data.service';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Observable, map, switchMap, tap } from 'rxjs';
import { MovieDataService } from './data.service';
import { MovieComplete } from './movie.models';
import { Title } from '@angular/platform-browser';
import { Constants } from '../../../app/constants/constant';

@Component({
selector: 'app-movie',
templateUrl: './movie.component.html'
})
export class MovieComponent implements OnDestroy, OnInit {
export class MovieComponent implements OnInit {
public movie: MovieComplete;
public movieId = '';
private movieSubscription: any;

constructor(private activatedRoute: ActivatedRoute, private dataService: DataService) {}
/* Replaced the subscription and handling it as observable with async pipe */
public movie$: Observable<MovieComplete>;

constructor(
private activatedRoute: ActivatedRoute,
private movieDataService: MovieDataService,
private title: Title
) {}

public ngOnInit() {
this.activatedRoute.params.pipe(tap(({ id }) => (this.movieId = id)));
this.movieSubscription = this.dataService.getMovie(this.movieId).pipe(tap((data) => (this.movie = data)));
}
this.title.setTitle(Constants.movieDetailPage);

public ngOnDestroy(): void {
this.movieSubscription.unsubscribe();
this.movie$ = this.activatedRoute.params.pipe(
map((params: Params) => params.id),
switchMap((movieId: string) => this.movieDataService.getMovie(movieId))
);
}
}
26 changes: 26 additions & 0 deletions FE/angular/src/app/components/movie/movie.models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface Movie {
imdbID: string;
Poster: string;
Title: string;
Type: string;
Year: string | number;
}

export interface MovieComplete extends MovieDetails {
Year: number;
}

export interface MovieDetails extends Movie {
Actors: string;
Director: string;
Genre: string;
Plot: string;
Rated: string;
Released: string;
Runtime: string;
Writer: string;
}

export interface MovieComplete extends MovieDetails {
Year: number;
}
16 changes: 16 additions & 0 deletions FE/angular/src/app/components/movie/movie.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { MovieRoutingModule } from './movie-routing.module';
import { MovieComponent } from './movie.component';
import { GoBackModule } from '../navigation/go-back/go-back.module';
import { GoImdbModule } from '../navigation/go-imdb/go-imdb.module';
import { MovieDataService } from './data.service';
import { MovieDetailModule } from '../movie-detail/movie-detail.module';

@NgModule({
declarations: [MovieComponent],
imports: [CommonModule, MovieRoutingModule, GoBackModule, GoImdbModule, MovieDetailModule],
providers: [MovieDataService]
})
export class MovieModule {}
Loading