Skip to content

Commit

Permalink
The version of sign int (#7)
Browse files Browse the repository at this point in the history
This is the version of sign in ,sign up and sign out.

Co-authored-by: Joseph Dvorak <[email protected]>
  • Loading branch information
xiaoshaotongzhi and dvorakjt authored Jun 2, 2024
1 parent 8526a20 commit a9e7a2f
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 51 deletions.
3 changes: 3 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const createJestConfig = nextJest({
const config = {
setupFiles: ['jest-canvas-mock'],
testEnvironment: 'jest-environment-jsdom',
//Provide an implementation of indexedDB for the LocalUserService class to access.
setupFiles: ['fake-indexeddb/auto'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
collectCoverage: true,
//add directories here to include them in coverage reports and threshold
collectCoverageFrom: ['./src/**'],
Expand Down
5 changes: 5 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//Provide structuredClone() as a global variable so that fake-indexeddb can
//access it.
import 'core-js/actual/structured-clone.js';
//Import reflect-metadata so that Inversify decorators work as expected.
import 'reflect-metadata';
33 changes: 33 additions & 0 deletions package-lock.json

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

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/reflect-metadata": "^0.1.0",
"builder-pattern": "^2.2.0",
"core-js": "^3.37.0",
"eslint": "^8",
"eslint-config-next": "14.1.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-storybook": "^0.8.0",
"fake-indexeddb": "^5.0.2",
"jest": "^29.7.0",
"jest-canvas-mock": "^2.5.2",
"jest-environment-jsdom": "^29.7.0",
Expand Down
60 changes: 41 additions & 19 deletions src/__tests__/services/classes/concrete/local-user-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,56 @@ import 'reflect-metadata';
import { UserType } from '@/model/enums/user-type';
import { LocalUserService } from '@/services/classes/concrete/local-user-service';
import { Subscription } from 'rxjs';

describe('LocalUserService', () => {
//TODO : Remove this file from replace these tests as each method in LocalUserService is implemented.
test('It throws an error when the unimplemented signUpWithEmail() method is called.', () => {
const userService = new LocalUserService();
expect(() =>
userService.signUpWithEmail(
'[email protected]',
'user',
1,
UserType.Challenger,
),
).toThrow();
let userService: LocalUserService;

beforeEach(() => {
userService = new LocalUserService();
});

test('It throws an error when the unimplemented signInWithEmail() method is called.', () => {
const userService = new LocalUserService();
expect(() => userService.signInWithEmail('[email protected]')).toThrow();
test('It successfully signs up a new user with valid data', async () => {
await expect(userService.signUpWithEmail(
'[email protected]',
'user',
1,
UserType.Challenger,
)).resolves.toBeUndefined();

// Additional assertions can be made to check if the user is correctly set, etc.
});

test('It throws an error when the unimplemented signOut() method is called.', () => {
const userService = new LocalUserService();
expect(() => userService.signOut()).toThrow();
test('It successfully signs in an existing user with valid email', async () => {
// Assuming a user with email '[email protected]' exists in the database
await expect(userService.signInWithEmail('[email protected]')).resolves.toBeUndefined();

// Additional assertions can be made to check if the user is correctly set, etc.
});

test('It throws an error when signing in with an invalid email', async () => {
await expect(userService.signInWithEmail('[email protected]')).rejects.toThrow('User not found');
});

test('It successfully signs out a signed-in user', async () => {
// Assuming a user is signed in before signing out
await userService.signInWithEmail('[email protected]');

userService.signOut();

expect(userService.user).toBeNull();
});

test('It throws an error when signing out without a signed-in user', () => {
expect(() => userService.signOut()).not.toThrow(); // No error expected when signing out without a signed-in user
});

test('When its subscribe() method is called, an RxJS Subscription is returned.', () => {
const subscription = userService.subscribe(() => {});
expect(subscription).toBeInstanceOf(Subscription);
});
//original version I store it.
test('When its subscribe() method is called, an RxJS Subscription is returned.', () => {
const userService = new LocalUserService();
const subscription = userService.subscribe(() => {});
expect(subscription).toBeInstanceOf(Subscription);
});
});
});
2 changes: 1 addition & 1 deletion src/__tests__/services/services-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { servicesContainer } from '@/services/services-container';
import { TYPES } from '@/services/types';
import { AbstractUserService } from '@/services/classes/abstract/abstract-user-service';

import "fake-indexeddb/auto";
describe('servicesContainer', () => {
it('Provides a subclass of the AbstractUserService service class.', () => {
const userService = servicesContainer.get<AbstractUserService>(
Expand Down
122 changes: 92 additions & 30 deletions src/services/classes/concrete/local-user-service.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,122 @@


import { injectable } from 'inversify';
import { Subject, type Observer, type Subscription } from 'rxjs';
import { AbstractUserService } from '../abstract/abstract-user-service';
import type { UserType } from '@/model/enums/user-type';
import type { Avatar } from '@/model/types/avatar.type';
import type { User } from '@/model/types/user';

//this import should be commented.
/**
* A mock user service for local development.
* A mock user service for local development that uses IndexedDB for data persistence.
*/
@injectable()
export class LocalUserService extends AbstractUserService {
private _user: User | null = null;
private userSubject: Subject<User | null> = new Subject<User | null>();
private db: IDBDatabase | null = null;
private readonly dbName: string = 'UserServiceDB';
private readonly storeName: string = 'users';
private dbReady: Promise<void>;

constructor() {
super();
this.dbReady = this.initializeDB(); // Directly assign the promise
}

private async initializeDB(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(new Error("Failed to open database"));
request.onupgradeneeded = (event: any) => {
const db = request.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, { keyPath: 'email' });
}
};
request.onsuccess = () => {
this.db = request.result;
resolve();
};
});
}

public get user(): User | null {
return this._user;
}

/**
* Updates the user and emits the updated value to subscribers.
*/
private set user(user: User | null) {
this._user = user;
this.userSubject.next(this.user);
}

/*
TODO : Implement the signUpWithEmail, signInWithEmail, and signOut methods
so that the app may be developed locally. For persisting data, you could use
IndexedDB, for example.
*/
signUpWithEmail(
email: string,
name: string,
avatar: Avatar,
type: UserType,
): Promise<void> {
throw new Error('Method not implemented.');
async signUpWithEmail(email: string, name: string, avatar: Avatar, type: UserType): Promise<void> {
await this.dbReady;
return new Promise<void>((resolve, reject) => {
if (!this.db) {
reject(new Error('Database is not initialized'));
return;
}
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);

const newUser: User = {
email,
name,
avatar,
type,
uid: 'unique-id-here',
completedActions: {
electionReminders: false,
registerToVote: false,
sharedChallenge: false
},
badges: [],
challengeEndDate: new Date().toISOString(),
completedChallenge: false,
redeemedAward: false,
contributedTo: [],
shareCode: 'default-share-code'
};

const request = store.add(newUser);

request.onsuccess = () => {
this.user = newUser;
resolve();
};
request.onerror = () => reject(new Error('Failed to sign up'));
});
}
signInWithEmail(email: string): Promise<void> {
throw new Error('Method not implemented.');

async signInWithEmail(email: string): Promise<void> {
await this.dbReady;
return new Promise<void>((resolve, reject) => {
if (!this.db) {
reject(new Error('Database is not initialized'));
return;
}
const transaction = this.db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(email);

request.onsuccess = () => {
if (request.result) {
this.user = request.result;
resolve();
} else {
reject(new Error('User not found'));
}
};
request.onerror = () => reject(new Error('Failed to sign in'));
});
}

signOut(): void {
throw new Error('Method not implemented.');
this.user = null;
}
/**
* Allows a subscriber to listen for updates to this service's user property.
*
* @param observerOrNext - An object containing callback functions to be
* executed when the userSubject emits a new value, or a single callback
* function to be called when a new value is emitted. For more information,
* see {@link https://rxjs.dev/guide/observer}.
*
* @returns An RxJS Subscription. For more information, see
* {@link https://rxjs.dev/guide/subscription}.
*/

subscribe(
observerOrNext:
| Partial<Observer<User | null>>
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
}

0 comments on commit a9e7a2f

Please sign in to comment.