Skip to content
Snippets Groups Projects
Commit 26b25a6a authored by Colin DAMON's avatar Colin DAMON
Browse files

Merge branch '41-trip-service-typescript' into 'master'

Resolve "Trip service (TypeScript)"

Closes #41

See merge request !48
parents 9fc19c5c 8fca4bec
No related branches found
No related tags found
1 merge request!48Resolve "Trip service (TypeScript)"
Showing
with 7006 additions and 0 deletions
......@@ -19,3 +19,4 @@ include:
- local: "/factory-patterns/.gitlab-ci.yml"
- local: "/exceptions/.gitlab-ci.yml"
- local: "/puissance-4/.gitlab-ci.yml"
- local: "/trip-service-kata-typescript/.gitlab-ci.yml"
......@@ -42,3 +42,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré
| [Factory patterns](/factory-patterns) | Code&coffee | Facile |
| [Exceptions](/exceptions) | Code&coffee | Facile |
| [Puissance 4](/puissance-4) | Kata | Moyenne |
| [Trip service (TypeScript)](/trip-service-kata-typescript) | Kata | Moyenne |
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
],
plugins: ['prettier', '@typescript-eslint'],
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'never',
},
],
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'always',
},
],
'prettier/prettier': [
'error',
{
singleQuote: true,
trailingComma: 'es5',
printWidth: 140,
},
],
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
},
'import/core-modules': ['sinon'],
},
parserOptions: {
parser: '@typescript-eslint/parser',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-unused-vars': ['off'],
'no-undef': ['off'],
},
},
],
};
/.idea/
trip-service-kata-typescript:
variables:
PROJECT_FOLDER: "trip-service-kata-typescript"
TZ: "Europe/Paris"
extends: .node14
only:
refs:
- master
- merge_requests
changes:
- ".gitlab-common-ci.yml"
- "trip-service-kata-typescript/**/*"
A Typescript port of the Trip Service Kata.
* Requirements: Recent version of node.js (tested on 8.9.4).
* Install Dependencies: `npm install`
* Run tests with file watching for fast feedback: `npm test -- --watch`
* Coverage `npm test -- --coverage`
\ No newline at end of file
# Trip Service Kata (TypeScript)
Découverte et résolution du kata [TripService](https://github.com/sandromancuso/trip-service-kata)
- **Auteur** : Anthony REY et Colin DAMON
- **Date** : 22/10/2020
- **Langage** : TypeScript
- **Niveau** : Moyen
- **Replay** : [Trip Service Kata en TypeScript avec Anthony & Colin](https://www.youtube.com/watch?v=EJgCY6CxDq0)
## Kata
Kata for legacy code hands-on session. The objective is to test and refactor the legacy TripService class.
The end result should be well-crafted code that express the domain.
You can watch the video with my solution. Although quite long, I explain my whole thought process while writting tests, how I break dependencies, the reasons for refactoring and re-desining the code (tests and production code), and why certain steps are important. I also cover how often I commit and why I do it.
The video is full of tips and tricks that can be used in any language.
https://www.youtube.com/watch?v=_NnElPO5BU0
module.exports = {
roots: ['<rootDir>/test'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
This diff is collapsed.
{
"name": "tripservicekata",
"version": "1.0.0",
"description": "Kata for a legacy code hands-on session. The objective is to write tests and refactor the given legacy code.",
"main": "index.js",
"author": "Daniel Perez <danipq26@gmail.com>",
"dependencies": {
"@types/jest": "^26.0.14",
"@typescript-eslint/eslint-plugin": "^4.1.1",
"@typescript-eslint/parser": "^4.1.1",
"eslint": "^7.9.0",
"eslint-import-resolver-typescript": "^2.3.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.4.2",
"prettier": "^2.1.2",
"sinon": "^9.2.0",
"ts-jest": "^26.3.0",
"typescript": "^4.0.2"
},
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:watch:all": "jest --watchAll",
"eslint:ci": "eslint './**/*.{ts,js}'",
"eslint": "eslint './**/*.{ts,js}' --fix"
},
"jest": {
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(js?|ts?)$"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sandromancuso/trip-service-kata.git"
},
"keywords": [
"kata"
],
"bugs": {
"url": "https://github.com/sandromancuso/trip-service-kata/issues"
},
"homepage": "https://github.com/sandromancuso/trip-service-kata",
"devDependencies": {
"@types/sinon": "^9.0.8"
}
}
export default class CollaboratorCallException extends Error {}
export default class UserNotLoggedInException extends Error {}
export default class Trip {}
import CollaboratorCallException from '../exception/CollaboratorCallException';
import User from '../user/User';
import Trip from './Trip';
export default class TripDAO {
public static findTripsByUser(user: User): Trip[] {
throw new CollaboratorCallException('TripDAO should not be invoked on an unit test.');
}
}
import UserNotLoggedInException from '../exception/UserNotLoggedInException';
import User from '../user/User';
import userSession, { UserSession } from '../user/UserSession';
import Trip from './Trip';
import TripDAO from './TripDAO';
export default class TripService {
public getTripsByUser(user: User): Trip[] {
return this.getTripsByUserAndSession(user, userSession, TripDAO.findTripsByUser);
}
public getTripsByUserAndSession(user: User, session: UserSession, findTripsByUser: (user: User) => Trip[]): Trip[] {
const loggedUser: User = session.getLoggedUser();
this.assertLoggedUser(loggedUser);
return this.isFriend(user, loggedUser) ? findTripsByUser(user) : [];
}
private isFriend(user: User, loggedUser: User) {
return user.getFriends().some((friend) => friend === loggedUser);
}
assertLoggedUser(user: User): void {
if (user == null) {
throw new UserNotLoggedInException();
}
}
}
import Trip from '../trip/Trip';
export default class User {
private trips: Trip[] = [];
private friends: User[] = [];
public getFriends(): User[] {
return this.friends;
}
public addFriend(user: User): void {
this.friends.push(user);
}
public addTrip(trip: Trip): void {
this.trips.push(trip);
}
public getTrips(): Trip[] {
return this.trips;
}
}
import CollaboratorCallException from '../exception/CollaboratorCallException';
import User from './User';
export class UserSession {
public getLoggedUser(): User {
throw new CollaboratorCallException('UserSession.getLoggedUser() should not be called in an unit test');
}
}
export default new UserSession();
import * as sinon from 'sinon';
import UserNotLoggedInException from '../src/exception/UserNotLoggedInException';
import Trip from '../src/trip/Trip';
import TripService from '../src/trip/TripService';
import User from '../src/user/User';
import { UserSession } from '../src/user/UserSession';
interface UserSessionStub extends UserSession {
getLoggedUser: any;
}
let service: TripService;
const stubSession = (user?: User) => {
const session: UserSessionStub = {
getLoggedUser: sinon.stub().returns(user),
};
return session;
};
describe('TripServiceShould', () => {
beforeEach(() => {
service = new TripService();
});
it('should not get trips without connected user', () => {
expect(() => service.getTripsByUserAndSession(new User(), stubSession(), () => [])).toThrow(UserNotLoggedInException);
});
it('should get empty trips for newcomer', () => {
const trips = service.getTripsByUserAndSession(new User(), stubSession(new User()), () => []);
expect(trips).toHaveLength(0);
});
it('Should get user trips', () => {
const authenticatedUser = new User();
const user = new User();
user.addFriend(authenticatedUser);
const tripToLyon = new Trip();
authenticatedUser.addTrip(tripToLyon);
const findTripsByUser = sinon.mock().withExactArgs(user).returns([tripToLyon]);
const trips = service.getTripsByUserAndSession(user, stubSession(authenticatedUser), findTripsByUser);
const [trip] = trips;
expect(trips).toHaveLength(1);
expect(trip).toEqual(tripToLyon);
});
});
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "dist/main.js",
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment