Replace custom Webpack builder with Angular DevKit builder, remove webpack.config.js, and add LoginComponent unit tests

This commit is contained in:
Milad Raeisi
2024-12-10 13:19:14 +04:00
parent e98fb20ced
commit d72d44d5de
6 changed files with 278 additions and 2868 deletions

View File

@@ -15,7 +15,7 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
@@ -89,7 +89,7 @@
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "angor:build:production"
@@ -101,10 +101,10 @@
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-builders/custom-webpack:extract-i18n"
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-builders/custom-webpack:karma",
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",

2926
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,10 +26,10 @@
class="mt-8"
[appearance]="'outline'"
[showIcon]="false"
[type]="secAlert.type"
[@shake]="secAlert.type === 'error'"
[type]="secAlert().type"
[@shake]="secAlert().type === 'error'"
>
{{ secAlert.message }}
{{ secAlert().message }}
</angor-alert>
@@ -154,10 +154,10 @@
class="mt-8"
[appearance]="'outline'"
[showIcon]="false"
[type]="menemonicAlert.type"
[@shake]="menemonicAlert.type === 'error'"
[type]="menemonicAlert().type"
[@shake]="menemonicAlert().type === 'error'"
>
{{ menemonicAlert.message }}
{{ menemonicAlert().message }}
</angor-alert>
<!-- Login form with Menemonic -->
<form

View File

@@ -0,0 +1,109 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
import { Router } from '@angular/router';
import { SignerService } from 'app/services/signer.service';
import { StateService } from 'app/services/state.service';
import { NostrLoginService } from 'app/services/nostr-login.service';
import { ReactiveFormsModule } from '@angular/forms';
import { of, throwError } from 'rxjs';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let mockRouter: jasmine.SpyObj<Router>;
let mockSignerService: jasmine.SpyObj<SignerService>;
let mockStateService: jasmine.SpyObj<StateService>;
let mockNostrLoginService: jasmine.SpyObj<NostrLoginService>;
beforeEach(async () => {
mockRouter = jasmine.createSpyObj('Router', ['navigateByUrl']);
mockSignerService = jasmine.createSpyObj('SignerService', ['handleLoginWithKey', 'handleLoginWithMnemonic', 'handleLoginWithExtension', 'getPublicKey', 'setPublicKey']);
mockStateService = jasmine.createSpyObj('StateService', ['loadUserProfile']);
mockNostrLoginService = jasmine.createSpyObj('NostrLoginService', ['getPublicKeyObservable', 'launchLoginScreen', 'launchSignupScreen']);
await TestBed.configureTestingModule({
declarations: [LoginComponent],
imports: [ReactiveFormsModule],
providers: [
{ provide: Router, useValue: mockRouter },
{ provide: SignerService, useValue: mockSignerService },
{ provide: StateService, useValue: mockStateService },
{ provide: NostrLoginService, useValue: mockNostrLoginService },
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
mockNostrLoginService.getPublicKeyObservable.and.returnValue(of('mockPublicKey'));
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should initialize forms on init', () => {
expect(component.SecretKeyLoginForm).toBeDefined();
expect(component.MenemonicLoginForm).toBeDefined();
});
it('should handle login with secret key', () => {
component.SecretKeyLoginForm.setValue({ secretKey: 'mockSecretKey', password: 'mockPassword' });
mockSignerService.handleLoginWithKey.and.returnValue(true);
component.loginWithSecretKey();
expect(mockSignerService.handleLoginWithKey).toHaveBeenCalledWith('mockSecretKey', 'mockPassword');
expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('/home');
});
it('should handle login with mnemonic', () => {
component.MenemonicLoginForm.setValue({ menemonic: 'mockMnemonic', passphrase: 'mockPassphrase', password: 'mockPassword' });
mockSignerService.handleLoginWithMnemonic.and.returnValue(true);
component.loginWithMenemonic();
expect(mockSignerService.handleLoginWithMnemonic).toHaveBeenCalledWith('mockMnemonic', 'mockPassphrase', 'mockPassword');
expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('/home');
});
it('should handle login with Nostr extension', async () => {
mockSignerService.handleLoginWithExtension.and.returnValue(Promise.resolve(true));
await component.loginWithNostrExtension();
expect(mockSignerService.handleLoginWithExtension).toHaveBeenCalled();
expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('/home');
});
it('should handle login failure with secret key', () => {
component.SecretKeyLoginForm.setValue({ secretKey: 'mockSecretKey', password: 'mockPassword' });
mockSignerService.handleLoginWithKey.and.returnValue(false);
component.loginWithSecretKey();
expect(component.loading()).toBeFalse();
expect(component.showSecAlert()).toBeTrue();
});
it('should handle login failure with mnemonic', () => {
component.MenemonicLoginForm.setValue({ menemonic: 'mockMnemonic', passphrase: 'mockPassphrase', password: 'mockPassword' });
mockSignerService.handleLoginWithMnemonic.and.returnValue(false);
component.loginWithMenemonic();
expect(component.loading()).toBeFalse();
expect(component.showMenemonicAlert()).toBeTrue();
});
it('should handle error during login with Nostr extension', async () => {
mockSignerService.handleLoginWithExtension.and.returnValue(Promise.reject('error'));
await component.loginWithNostrExtension();
expect(component.loading()).toBeFalse();
});
});

View File

@@ -1,6 +1,6 @@
import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert';
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, inject, signal } from '@angular/core';
import {
FormBuilder,
FormGroup,
@@ -40,37 +40,33 @@ import { Subscription } from 'rxjs';
export class LoginComponent implements OnInit {
SecretKeyLoginForm: FormGroup;
MenemonicLoginForm: FormGroup;
secAlert = { type: 'error' as AngorAlertType, message: '' };
showSecAlert = false;
secAlert = signal({ type: 'error' as AngorAlertType, message: '' });
showSecAlert = signal(false);
menemonicAlert = { type: 'error' as AngorAlertType, message: '' };
showMenemonicAlert = false;
menemonicAlert = signal({ type: 'error' as AngorAlertType, message: '' });
showMenemonicAlert = signal(false);
loading = false;
isInstalledExtension = false;
loading = signal(false);
isInstalledExtension = signal(false);
privateKey: Uint8Array = new Uint8Array();
publicKey: string = '';
npub: string = '';
nsec: string = '';
useNostrLogin = true;
publicKey = signal('');
npub = signal('');
nsec = signal('');
useNostrLogin = signal(true);
private subscription: Subscription;
constructor(
private _formBuilder: FormBuilder,
private _router: Router,
private _signerService: SignerService,
private _stateService: StateService,
private _nostrLoginService: NostrLoginService
) { }
private _formBuilder = inject(FormBuilder);
private _router = inject(Router);
private _signerService = inject(SignerService);
private _stateService = inject(StateService);
private _nostrLoginService = inject(NostrLoginService);
ngOnInit(): void {
this.subscription = this._nostrLoginService.getPublicKeyObservable().subscribe({
next: (pubkey: string) => {
this.publicKey = pubkey;
this.publicKey.set(pubkey);
this._signerService.setPublicKey(pubkey);
this.initializeAppState();
this._router.navigateByUrl('/home');
@@ -120,9 +116,9 @@ export class LoginComponent implements OnInit {
globalContext.nostr &&
typeof globalContext.nostr.signEvent === 'function'
) {
this.isInstalledExtension = true;
this.isInstalledExtension.set(true);
} else {
this.isInstalledExtension = false;
this.isInstalledExtension.set(false);
}
}
@@ -134,8 +130,8 @@ export class LoginComponent implements OnInit {
const secretKey = this.SecretKeyLoginForm.get('secretKey')?.value;
const password = this.SecretKeyLoginForm.get('password')?.value;
this.loading = true;
this.showSecAlert = false;
this.loading.set(true);
this.showSecAlert.set(false);
try {
const success = this._signerService.handleLoginWithKey(
@@ -152,12 +148,9 @@ export class LoginComponent implements OnInit {
}
} catch (error) {
// Handle login failure
this.loading = false;
this.secAlert.message =
error instanceof Error
? error.message
: 'An unexpected error occurred.';
this.showSecAlert = true;
this.loading.set(false);
this.secAlert.update(alert => ({ ...alert, message: error instanceof Error ? error.message : 'An unexpected error occurred.' }));
this.showSecAlert.set(true);
console.error('Login error: ', error);
}
}
@@ -172,8 +165,8 @@ export class LoginComponent implements OnInit {
this.MenemonicLoginForm.get('passphrase')?.value || ''; // Optional passphrase
const password = this.MenemonicLoginForm.get('password')?.value;
this.loading = true;
this.showMenemonicAlert = false;
this.loading.set(true);
this.showMenemonicAlert.set(false);
const success = this._signerService.handleLoginWithMnemonic(
menemonic,
@@ -185,9 +178,9 @@ export class LoginComponent implements OnInit {
this.initializeAppState();
this._router.navigateByUrl('/home');
} else {
this.loading = false;
this.menemonicAlert.message = 'Menemonic is missing or invalid.';
this.showMenemonicAlert = true;
this.loading.set(false);
this.menemonicAlert.update(alert => ({ ...alert, message: 'Menemonic is missing or invalid.' }));
this.showMenemonicAlert.set(true);
}
}

View File

@@ -1,24 +0,0 @@
const webpack = require('webpack');
module.exports = {
resolve: {
fallback: {
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
// url: require.resolve('url/'),
url: false,
},
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser',
}),
new webpack.NormalModuleReplacementPlugin(
/node:crypto/,
require.resolve('crypto-browserify')
),
new webpack.DefinePlugin({
global: 'globalThis',
}),
],
};