Coding - djsordo/senda GitHub Wiki

Fechas con sentido

La funcion formatDateUtil convierte un dato fecha de javascript en una cadena de texto como "mañana (03/04)", o "hace una hora (16:05)".

Su uso es relativamente simple:

 import { formatDateUtil } from './string-util';

 formatDateUtil( tomorrow ) -> retorna "mañana (dd/mm)"

Más adelante seguramente se cambie para que en lugar de un dato tipo Date coja un dato tipo timestamp, que tiene más sentido.

Crear entradas de firestore con nuestros propios id's

Si usamos la función addDoc de firestore, ésta creará un id propio que no informa mucho, por ejemplo eC38vvLRv9JNYBKAno60.

En cambio, si usamos setDoc le podemos poner el id que queramos, siempre y cuando tenga letras y números... para este fin he creado la función make_id. Usarla es sencillo:

Por ejemplo, los literales Raúl Luna quedarian convertidos en raul_luna, permitiendo que se cree un id con ese valor perfectamente. Para invocarlo:

import { make_id } from 'string-util';

make_id( 'esto', 'es', 'una', 'prueba' ) => 'esto_es_una_prueba'

Sobre localStorage y testabilidad de la aplicación

La api localStorage es expuesta por muchos navegadores para acceder a una base de datos "local" en formato clave/valor. Hasta aquí bien.

Sin embargo, el uso de esta api directamente provoca un problema de testabilidad de la aplicación.

Veamos este código:

private async initCurrentUser(){
  this.usuarioService.getUsuarioBD( localStorage.getItem('emailUsuario') )
  .subscribe(usuarios => {
    this.usuario = usuarios[0];
    console.log('usuario: ', usuarios);
  });
}

En el componente, mientras ejecutemos la aplicación, la llamada a localStorage funcionará y traerá un valor del localStorage que ha sido guardado en pantallas previas.

Pero se plantea un problema cuando queramos testar el componente aislado: en ese momento se ejecutará en un navegador instrumentalizado que viene "limpio": el localStorage no tendrá ningún valor almacenado previamente.

¿Podemos permitir la testabilidad de un componente y a la vez seguir accediendo a localStorage???

Si. La inyección de dependencias de Angular nos permitirá inyectar un servicio con una implementación "real" en la aplicación en producción y a la vez un servicio con una implementación para pruebas en los tests.

Paso 1: creamos el servicio LocalStorage que será nuestro mock

@Injectable({
  providedIn : 'root'
})
export class LocalStorage{

  values : Map<string,string>;

  constructor() {
    this.values = new Map<string,string>();
  }

  setValues( values : [{key:string, value:string}] ){
    for( let value of values ){
      this.values.set( value.key, value.value );
    }
  }

  setItem( key : string, value : string ) : void {
    this.values.set( key, value ); 
  }

  getItem( key : string ) : string {
    return this.values.get( key );
  }

  removeItem( key : string  ) : void {
    this.values.delete( key );
  }

}

Esta es una clase normal que guarda los grupos clave/valor en un mapa.

A continuación lo que haremos será crear la clase que implemente la funcionalidad real, accediendo de verdad a localStorage:

@Injectable({
providedIn : 'root'
})
export class LocalStorageService extends LocalStorage {

setItem(key: string, value: string): void {
    localStorage.setItem( key, value );
}
getItem(key: string): string {
    return localStorage.getItem( key );
}
removeItem(key: string): void {
    localStorage.removeItem( key );
}

}

Paso 2: declarar el componente para que cada vez que se registre, llame al servicio real y no al mock

Ahora viene la magia: cuando vayamos a usar esta clase en nuestra aplicación, la declararemos así:

providers: [{ provide : LocalStorage, 
            useClass : LocalStorageService }],

Esto se puede colocar en app.module.ts para que esté disponible para toda la aplicación o bien puede declararse por componente.

Paso 3: hacer las modificaciones correspondientes en los tests

En los tests, lo haremos al revés: nos aseguraremos no sólo de inyectar el servicio de pruebas, sino además de meterle los valores que necesita nuestro componente para funcionar correctamente:

beforeAll( ( callMeOnFinish ) => {
  TestBed.configureTestingModule({
    imports: [ ... ],
      providers: [{ provide : LocalStorage, useFactory: () => {
        let localStorage = new LocalStorage(); 
        localStorage.setValues( [{key : 'emailUsuario', 
                                  value : '[email protected]'}]);
        return localStorage;
        } } ]  
  });
  callMeOnFinish();
} );

En este caso, en la inyección de dependencias, hemos usado useFactory, que nos permite insertar unos valores de pruebas para que el test vaya bien.