Commit 5daf6826 authored by Raphaël MARQUES's avatar Raphaël MARQUES
Browse files

setup

parents
/node_modules
# Betclic Coding Assignment 2020
The goal of this assignment is to showcase your ability to develop features and your coding style.
Due to the time constraint you will have to prioritize what you work on,
and have to try to balance cleanliness with just getting it done.
Even though the app is small, one can easily spend the whole week working on it:
perfecting styles, testing every single method, or carefully crafting every single line of code.
Please don't! Do as much as you can in about **two hours** and share the results.
The most important part of the interview will come after this one, when we look at the app together,
talk about the decisions you have made, etc...
## Ticketing Managing Application
Build a ticket managing app, where the user can add, filter, assign, and complete tickets.
* The app should have two screens: the list screen, and the details screen.
Please use the Angular router to manage the transitions between them.
* Even though we tend to use NgRx for state management, you can use a different approach if you think it fits better.
* Write a couple of tests. The goal here is not to build a production-quality app, so don't test every single detail.
Two or three tests should be good enough.
* Don't forget about error handling and race conditions. The API server has a random delay.
If you bump it up to say 10 seconds, would the app still work correctly?
## Good To Know
* Application must have automated tests (type and completeness of the tests are up to you).
* Application should be optimized for performance.
* (Optional) You can use whatever design library you want, if you even want one.
* (Optional) Application should be split in domain libraries.
* (Optional) Application should have a good User Experience.
## Submitting your solution
Please send us the link to your publicly hosted git repository.
We will continue to work on it during the pair-programming sessions.
Please also indicate approximately how long you spent on the submission.
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"demo": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputPath": "dist/demo",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "demo:build"
},
"configurations": {
"production": {
"browserTarget": "demo:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "demo:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"demo-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "demo:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "demo"
}
{
"name": "betclic-coding-assignment-2020",
"version": "0.0.0",
"private": true,
"dependencies": {
"@angular/common": "10.1.4",
"@angular/compiler": "10.1.4",
"@angular/core": "10.1.4",
"@angular/forms": "10.1.4",
"@angular/platform-browser": "10.1.4",
"@angular/platform-browser-dynamic": "10.1.4",
"@angular/router": "10.1.4",
"core-js": "2.6.2",
"rxjs": "6.6.3",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"postinstall": "ngcc"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1001.4",
"@angular/cli": "~10.1.4",
"@angular/compiler-cli": "~10.1.4",
"@angular/language-service": "~10.1.4",
"@types/node": "^12.11.1",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~7.0.0",
"tslint": "~6.1.0",
"typescript": "~4.0.3"
}
}
<h2>Tickets</h2>
<ul>
<li *ngFor="let ticket of tickets$ | async">
Ticket: {{ ticket.id }}, {{ ticket.description }}
</li>
</ul>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AppComponent]
});
fixture = TestBed.createComponent(AppComponent);
});
it('should create the app', () => {
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'app'`, () => {
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
});
it('should render title in a h1 tag', () => {
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
});
});
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Ticket } from '../interfaces/ticket.interface';
import { User } from '../interfaces/user.interface';
import { BackendService } from './backend.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public readonly users$: Observable<User[]> = this.backendService.users();
public readonly tickets$: Observable<Ticket[]> = this.backendService.tickets();
constructor(private readonly backendService: BackendService) {}
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BackendService } from './backend.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [BackendService],
bootstrap: [AppComponent]
})
export class AppModule {
}
import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { delay, tap } from 'rxjs/operators';
import { Ticket } from '../interfaces/ticket.interface';
import { User } from '../interfaces/user.interface';
/**
* This service acts as a mock back-end.
* It has some intentional errors that you might have to fix.
*/
function randomDelay() {
return Math.random() * 4000;
}
@Injectable()
export class BackendService {
public storedTickets: Ticket[] = [
{
id: 0,
completed: false,
assigneeId: 111,
description: 'Install a monitor arm'
},
{
id: 1,
completed: false,
assigneeId: 111,
description: 'Move the desk to the new location'
}
];
public storedUsers: User[] = [{ id: 111, name: 'Victor' }];
private lastId: number = 1;
private findUserById = id => this.storedUsers.find((user: User) => user.id === +id);
private findTicketById = id => this.storedTickets.find((ticket: Ticket) => ticket.id === +id);
public tickets(): Observable<Ticket[]> {
return of(this.storedTickets).pipe(delay(randomDelay()));
}
public ticket(id: number): Observable<Ticket> {
return of(this.findTicketById(id)).pipe(delay(randomDelay()));
}
public users(): Observable<User[]> {
return of(this.storedUsers).pipe(delay(randomDelay()));
}
public user(id: number): Observable<User> {
return of(this.findUserById(id)).pipe(delay(randomDelay()));
}
public newTicket(payload: { description: string }): Observable<Ticket> {
const newTicket: Ticket = {
id: ++this.lastId,
completed: false,
assigneeId: null,
description: payload.description
};
return of(newTicket).pipe(
delay(randomDelay()),
tap((ticket: Ticket) => this.storedTickets.push(ticket))
);
}
public assign(ticketId: number, userId: number): Observable<Ticket> {
const user = this.findUserById(+userId);
const foundTicket = this.findTicketById(+ticketId);
if (foundTicket && user) {
return of(foundTicket).pipe(
delay(randomDelay()),
tap((ticket: Ticket) => {
ticket.assigneeId = +userId;
})
);
}
return throwError(new Error('ticket or user not found'));
}
public complete(ticketId: number, completed: boolean): Observable<Ticket> {
const foundTicket = this.findTicketById(+ticketId);
if (foundTicket) {
return of(foundTicket).pipe(
delay(randomDelay()),
tap((ticket: Ticket) => {
ticket.completed = true;
})
);
}
return throwError(new Error('ticket not found'));
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Betclic Coding Assignment 2020</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
export interface Ticket {
id: number;
completed: boolean;
assigneeId: number;
description: string;
}
export interface User {
id: number;
name: string;
}
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/my-app'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
import './polyfills';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
// Ensure Angular destroys itself on hot reloads.
if (window['ngRef']) {
window['ngRef'].destroy();
}
window['ngRef'] = ref;
// Otherwise, log the boot error
}).catch(err => console.error(err));
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** Evergreen browsers require these. **/
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.
\ No newline at end of file
/* You can add global styles to this file, and also import other style files */
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
},
"files": [
"main.ts",
"polyfills.ts"
],
"include": [
"**/*.d.ts"
]
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment