Aplicaciones web con un hamster gafapasta

Text

quiénes somos

Juanan

juanantonio.gomez@beeva.com

Bea

beatriz.demiguel@beeva.com

¿qué neCESITAMOS ANTES DE EMPEZAR?

  • `ember ` Todos los comandos de ember-cli empiezan por "ember"

 

  • `new` crea el directorio de tu proyecto, un repositiorio git local y hace el primer commit por ti

 

  • `foodme` será el nombre de nuestro proyecto

  $ ember new foodme

PARA CREAR UN NUEVO PROYECTO

Si clonas el repo de ejemplo no tendrás que hacerlo, pero recuérdalo para tu próximo proyecto :)

Let's start!

  $ npm install -g ember-cli

  $ cd foodme
  $ npm install

> Recomendación del chef: <

instala Ember Inspector

MVC (o algo así)

Templates
HTML + Handlebars = HTMLBars

Models
Manejo y/o persistencia de datos

Routes
Puntos de entrada de la app.
Definen los distintos estados.

OTROS ELEMENTOS DE EMBER

 

  • Controllers: contienen lógica

 

  • Services: objetos globales con glamour

 

  • Helpers: funcioncillas en templates

 

  • Mixins: no escribas JS dos veces

 

  • Partials: no escribas HTML dos veces

 

  ./foodme$ ember serve

ARRANCA EL SERVIDOR

rOUTEs

  $ ember generate route restaurants

genera una ruta

 // app/routes/restaurants.js  

 export default Route.extend({
 });
 // app/router.js

 Router.map(function() {
     this.route('restaurants');
 });
 // app/templates/restaurants.hbs  

 {{outlet}}

ember-cli añade la ruta 'restaurants' al router:

y crea dos ficheros:

la ruta

la plantilla

AÑADE un MODELO

 // app/routes/restaurants.js  
 import Route from '@ember/routing/route';

 export default Route.extend({
     model() {
         return {
             foo: 'bar'
         }
     }
 });

 

 // app/templates/restaurants.hbs  
 <h1>{{model.foo}}</h1>

 // RESULT  
 <h1>bar</h1>

Para evaluar variables en plantilla usamos llaves

{{ someVariable }}

Consume datos

// app/routes/restaurants.js

import Route from '@ember/routing/route';
import $ from 'jquery';

const host = 'https://raw.githubusercontent.com/shokmaster/ember-workshops-3/master/';

export default Route.extend({

  model: function() {
    return $.getJSON(`${host}resources/restaurants.json`);
  }

});
// app/templates/restaurants.hbs

<ul>
  {{#each model as |restaurant| }}
    <h4>{{restaurant.name}}</h4>

    <p>{{restaurant.description}}</p>
  {{/each}}
</ul>

Vamos a mostrar un listado de restaurantes

Iteramos con operador de bloque each

{{#each someArray as |item| }}

       ... print this for each item...

 {{/each}}

Tenemos jquery disponible

application template

Es la plantilla por defecto, y se renderiza en todas las rutas

      <h1>Welcome to Ember</h1>
      {{outlet}}   <-- ~ "Lo que tengas, ponlo aquí"

AÑADE BOOTSTRAP

// En app/templates/restaurants.hbs damos forma al listado:

<div class="row-fluid">
    <div class="col-md-12">
        <div class="panel panel-default">
            <div class="panel-heading"><h3 class="panel-title">Restaurantes encontrados</h3></div>
            <div class="panel-body">

                {{! LISTA DE RESTAURANTES }}
                {{#each model as |restaurant|}}
                    <div class="media">
                        <a class="media-left"><img class="media-object" src="#"></a>
                        <div class="media-body">
                            <h4 class="media-heading">{{restaurant.name}}</h4>
                            {{restaurant.description}}
                        </div>
                    </div>
                {{/each}}

            </div>
        </div>
    </div>
</div>
// En app/index.html añadimos las CSS de Bootstrap:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">
// En templates/application.hbs añadimos un container de Bootstrap:

<div class="container-fluid">{{outlet}}</div>

Nos ocuparemos de las imágenes más adelante

¡no tenemos menú!

Vamos a añadir un menú de navegación a nuestra web

 

Utilizaremos el HTML de Bootstrap:

https://getbootstrap.com/components/#navbar

Application Controller

nav-menu Component

Datos:

  • Nombre de nuestra web
  • Lista de links del menú

Acciones:

  • Abrir/cerrar menú
  • Cambiar idioma

AÑADIENDO un menú (1) (2)

  $ ember g component nav-menu

  $ ember g controller application

2. Creamos el application controller:

1. Creamos el componente nav-menu:

AÑADIENDO UN MENÚ (3)

// En app/controllers/application.js definimos los datos que le pasaremos al componente nav-menu:
//  * Nombre de nuestra aplicación
//  * Lista de enlaces para el menú

import Controller from '@ember/controller';

export default Controller.extend({

  appName: 'FoodMe',

  menuLinks: [{
    name: 'Restaurantes',
    url: 'restaurants'
  }]

});

3. Rellenamos el application controller:

AÑADIENDO UN MENÚ (4)

// En app/components/nav-menu.js
//  * Variable isMenuOpened para controlar el estado abierto/cerrado

import Component from '@ember/component';

export default Component.extend({

    isMenuOpened: false,

    actions: {
        toggleMenu(){
            this.toggleProperty('isMenuOpened');
        }
    }
});

4. Rellenamos el JavaScript del componente:

Utilizamos la función toggleProperty que equivale a un set con el valor inverso:

this.toggleProperty('myboolean')

equivale a:

const myboolean = this.get('myboolean');

this.set('myboolean',  !myboolean)

La propiedad actions tiene todas las acciones:          actions: {

                   functionAction1(){..},

                   functionAction2(){..},

                    ..

              } 

AÑADIENDO UN MENÚ (5)

// En app/templates/components/nav-menu.hbs ponemos el HTML del menú:

<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="navbar-header">
            {{! Collapsed menu for small screens }}
            <button type="button" {{action 'toggleMenu'}} class="navbar-toggle" data-toggle="collapse" data-target="#navbar" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>

            {{! Brand with link}}
            {{link-to appName "index" class="navbar-brand"}}
        </div>

        {{! Add class "in" to show items in small screen when menu is open }}
        <div class="collapse navbar-collapse {{if isMenuOpened 'in'}}" id="navbar">
            {{! Menu links}}
            <ul class="nav navbar-nav">
        	{{#each menuLinks as |link|}}
                    <li>{{link-to link.name link.url}}</li>
                {{/each}}
            </ul>
        </div>
         {{!@TODO cambio de idioma }}
    </div>
</nav>

5. Rellenamos el template del componente:

Utilizamos el helper action para que al hacer click lance la acción del controlador llamada 'toggleMenu'

{{action 'myActionName'}}

Utilizamos el componente link-to para crear un link a una ruta de nuestra app

{{link-to 'nameToShow' 'routeName'}}

Utilizamos el helper if para añadir una clase

{{if someBooleanProperty 'classToAdd'}}

AÑADIENDO UN MENÚ (6)

// En app/templates/application.hbs añadimos el componente y le pasamos
// las propiedades que definimos en el controller/application.js

{{nav-menu
  appName=appName
  menuLinks=menuLinks}}

<div class="container-fluid">
  {{outlet}}
</div>

6. Añadimos el componente al template application:

HABEMUS MENÚ

Y este es el resultado:

Añade un helper

  $ ember generate helper restaurant-img

- Mueve las imágenes que hay en la carpeta resources/img/restaurants

a la carpeta de tu proyecto public/img/restaurants

{{restaurant-img 'tofuparadise'}}

img/restaurants/tofuparadise.jpg

Escribiendo esto:

Queremos obtener esto:

- Modifica el helper restaurant-img para que devuelva el path de la imagen de cada restaurante:

Pista: el parámetro que recibirá nuestro helper es la id del restaurante

con fotos mucho mejor

Y este es el resultado:

Ejercicio por parejas

Genera una ruta 'about' añadiendo algún contenido

Recuerda añadir un enlace en el menú

Los addons son complementos para tu app

addon ~ plugin

utiliza addons

Consulta los addons disponibles en http://emberobserver.com/

instala el addon i18n

  • `install` incluye un addon en tu proyecto como dependencia en el package.json

 

  • `ember-i18n` es el nombre del addon que vamos a instalar. Sirve para traducir tu app de manera sencilla.

  $ ember install ember-i18n

Documentación de este addon:

https://github.com/jamesarosen/ember-i18n/wiki

el Helper 't'

El helper t sirve para traducir textos. Recibe como parámetro una key, busca su traducción y la devuelve.

this.get('i18n').t('restaurants')

    { "restaurants": "Restaurantes"}

{{t 'restaurants'}}  // Pinta "Restaurantes"

{{#some-component param=(t 'restaurants')}}

- Traducción en app/locales/es/translations.js

- Uso en los .hbs:

o, si estamos ya dentro de otro helper:

- Uso en .js:

el servicio i18n

El servicio 18n está disponible en toda la aplicación, solo tenemos que inyectarlo. Tiene una propiedad locale que indica el idioma actual.

- Inyecta el servicio en tu componente/controlador/ruta, etc:

i18n: inject()

- Accede al servicio y setea el idioma:

                                         const i18nService = this.get('i18n')

                                         i18nService.set('locale', 'es');

añadiendo traducciones

  $ ember generate locale en                    // Generamos ficheros de inglés

  $ ember generate locale es                    // Generamos ficheros de español

1. Genera los ficheros de traducciones:

2. Hemos preparado el contenido de los ficheros por ti.

 

    Copia el contenido de estos ficheros:

          resources/translations/en/translations.js

          resources/translations/es/translations.js

    en:

          app/locales/en/translations.js

          app/locales/es/translations.js

traduciendo textos

    // MODIFICA app/controllers/application.js

    ...
    menuLinks: [{
        label: 'restaurants',
        url: 'restaurants'
    }, {
        label: 'about',
        url: 'about'
    }]

    ...

    // MODIFICA app/templates/components/nav-menu.hbs

    ...

    {{#each menuLinks as |link|}}
            <li>{{link-to (t link.label) link.url}}</li>
    {{/each}}

    ...

Ejercicio por parejas

 

Crea un helper llamado weekday que reciba un

número y devuelva el dia de la semana. Por ejemplo:

{{weekday 3}} // pinta 'wednesday'

cambiando de idioma

//MODIFICA app/components/nav-menu.js
import Component from '@ember/component';
import { inject } from '@ember/service';
import { observer } from '@ember/object';

const languages = [{
	name: 'Español',
	code: 'es'
}, {
	name: 'English',
	code: 'en'
}];

export default Component.extend({

  ...

  i18n: inject(),

  languages,

  selectedLanguage: languages[1],

  onSelectedLanguageChange: observer('selectedLanguage', function() {
    this.set('i18n.locale', this.get('selectedLanguage.code'));
  }),

  ...

});

Definimos el array de idiomas

Definimos una propiedad para saber el idioma seleccionado y la inicializamos

Definimos un observer que cambiará el idioma cuando el selector cambie

Helper MUT

- mut permite especificar que un componente secundario puede actualizar el valor (mutable) que se le pasa. Cambia el valor del componente principal.

 

 

 

 

 ha

 

{{my-component updateAction=(action 'updateValue')}}
export default Ember.Controller.extend({ 
    actions: { 
        updateValue(newValue) { 
            this.set('model.value', newValue); 
        }
    } 
});
{{ my-component 
  updateAction=(action (mut model.value))
}}

cambiando de idioma

  $ ember install ember-power-select

3. Instala ember-power-select para utilizar su componente dropdown

Documentación de este addon:

http://www.ember-power-select.com

// Incluye en app/templates/components/nav-menu.hbs donde pone "@TODO cambio de idioma"

<ul class="nav navbar-nav navbar-right">
    <li>
        {{#power-select
            selected=selectedLanguage
            options=languages
            class='dropdown'
            onchange=(action (mut selectedLanguage))
            as |language|}}
               {{language.name}}
        {{/power-select}}
    </li>
</ul>

well done!

Y este es el resultado:

testeando

  $ ember g helper-test weekday --test-type unit

Creamos un tet unitario para el helper 'weekday'

import { module, test } from 'qunit';
import { weekday, names } from 'foodme/helpers/weekday';

module('Unit | Helper | weekday', function() {

  test('it returns the day number', async function(assert) {

    ...

  });

  test('it returns an empty string when receives an incorrect day number', async function(assert) {

    ...

  });

});

segunda parte

ANTES DE EMPEZAR...

Cambiamos a la rama '06-filter-restaurants':

  $ git fetch --all

  $ git checkout 06-filter-restaurants

Propiedades computadas

Las propiedades computadas permiten declarar funciones como propiedades. Se declaran llamando a Ember.computed.

import EmberObject, { computed } from '@ember/object';

let Person = EmberObject.extend({
  firstName: null,

  lastName: null,

  fullName: computed('firstName', 'lastName', function() {
    const firstName = this.get('firstName');
    const lastName  = this.get('lastName');

    return `${firstName} ${lastName}`;
  })
});
let tom = Person.create({
  firstName: 'Tom',
  lastName: 'Dale'
});

tom.get('fullName') // 'Tom Dale'

Argumentos que recibe:

- Las propiedades de las que depende: si cambian, se ejecuta la función.

- La función que retorna el valor deseado: sólo se llama una vez y el resultado se almacena en caché hasta que las propiedades dependientes cambien.

Helpers de ember

- concat concatena strings {{concat firstName ' ' lastName}}

- get propiedades de un objeto (get person 'name')

- hash crea objetos {{hash name='Sarah' title=office}} 

- Helpers para debugear: debugger (breakpoints), log (logs de variables)

- each-in itera sobre las propiedades de un objeto

- with alias de una propiedad a un nuevo nombre {{#with user.posts as |blogPosts|}}

- query-params pasa parámetros cuando se transiciona a una ruta:

                {{transition-to 'my-route' (query-params foo='bar')}}

    equivale a:       this.transitionTo('myRoute', { queryParams: { foo: 'bar'}})

Filtra los restaurantes (1)

  $ ember g controller restaurants

import Controller from '@ember/controller';
import { A } from '@ember/array';
import { computed } from '@ember/object';
import { isPresent } from '@ember/utils';

export default Controller.extend({

    cuisineOptions: computed('model', function() {
        return this.get('model').mapBy('cuisine').filter((elem, pos, arr) => arr.indexOf(elem) === pos).map((cuisine) => {
            return {
                name: cuisine,
                title: cuisine.replace('/', ' / ').capitalize()
            };
        });
    }),

    // Filter criterias
    filterCuisins: A(),

    // Filter function
    filteredRestaurants: computed('model', 'filterCuisins', function() {
        let filteredRestaurants = this.get('model');

        const filterCuisins = this.get('filterCuisins');

        // Filter by cuisine
        if (isPresent(filterCuisins)) {
            filteredRestaurants = filteredRestaurants.filter((item) =>
                filterCuisins.mapBy('name').includes(item.cuisine)
            );
        }

        return filteredRestaurants;
    })
});

Definimos una propiedad que genera el array con todos los tipos de cocina

Definimos una propiedad computada que depende de los tipos de cocina seleccionados y retorna los restaurantes filtrados

FILTRA los restaurantes (2)

{{! app/templates/restaurants.hbs }}
<div class="row-fluid">
  <div class="col-md-4">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title">{{t 'filterRestaurants'}}</h3>
      </div>

      <div class="panel-body">
        {{! @TODO filter rating }}
      </div>

      <div class="panel-body">
        <label>{{t 'cuisine'}}</label>
        {{#power-select-multiple
          options=cuisineOptions
          selected=filterCuisins
          placeholder=(t 'selectCuisins')
          onchange=(action (mut filterCuisins))
          as |cuisine|}}
          {{cuisine.title}}
        {{/power-select-multiple}}
      </div>

      <div class="panel-body">
        {{! @TODO filter name }}
      </div>

      <div class="panel-footer">
        {{! @TODO button clear }}
      </div>
    </div>
  </div>

  <div class="col-md-8">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title">{{t 'foundRestaurants' count=filteredRestaurants.length}}</h3>
      </div>

      <div class="panel-body">
        {{#each filteredRestaurants as |restaurant| }}
          <div class="media">
            <a class="media-left">
              <img class="media-object" src={{restaurant-img restaurant.id}} width="64" height="64">
            </a>
            <div class="media-body">
              <h4 class="media-heading">
                {{restaurant.name}} <span class="label label-info">{{restaurant.cuisine}}</span>
              </h4>
              {{restaurant.description}}
            </div>
          </div>
        {{/each}}
      </div>
    </div>
  </div>
</div>

Utilizamos el selector múltiple del addon ember-power-select

Mostramos los restaurantes filtrados en lugar del modelo

Utilizamos el helper action combinado con el helper mut :

El componente invocaría la acción con el nuevo valor seleccionado. cambia el valor selectedCuisins a lo que se proporcionó como argumento de acción.

Ejercicio por parejas

  1. Instala el addon  ember-cli-star-rating.
  2. Muestra en el listado de restaurantes la valoración de cada uno de ellos

NESTED ROUTES

Vamos a generar rutas anidadas a partir de la ruta 'restaurants'.

- restaurants/index mostrará todos los restaurantes:

Router.map(function() {
  ...
  this.route('restaurants', function() {
    this.route('detail', { path: 'detail/:id' });
  });
});

  $ ember g route restaurants/index

  $ ember g route restaurants/detail

http://localhost:4200/restaurants/detail/[:id]

Si abrimos el router (app/router) nada ha cambiado. Esto es porque 'index' funciona como ruta predeterminada y, si no tenemos nada que añadir al comportamiento por defecto, no hace falta definirla.

- restaurants/detail mostrará el detalle de un restaurante:

Para indicarle a la aplicación a qué restaurante queremos acceder, necesitamos reemplazar el path de la ruta 'detail' con la ID del restaurante.

Ver esquema aquí

Nested Index Route

 - Mueve también la plantilla de la ruta principal a la plantilla de la ruta restaurants. Deja la ruta principal con un {{outlet}}.

 - Elimina el controlador padre y genera el controlador de la ruta hija con lo que tenía el padre

  $ ember g controller restaurants/index

 - Si no definimos modelo, la ruta anidada hereda el modelo de la ruta padre.

Nested detail Route

 Genera el modelo buscando por id:

Añade el restaurante a la plantilla:

// app/routes/restaurants/detail.js
import Route from '@ember/routing/route';

export default Route.extend({

    model({id}){
        const parentModel = this.modelFor('restaurants') || [];
        return parentModel.findBy('id', id);
    }

});
{{! app/templates/restaurants/detail.hbs }}
{{model.description}}

Comprueba el resultado abriendo en el navegador:

http://localhost:4200/#/restaurants/detail/esthers

Nested detail Route

Plantilla con estilo para la ruta de detalle:

<div class="row-fluid">
   <div class="page-header">
      <h1>{{model.name}}</h1>
   </div>

   <div class="col-md-3">
      <img src="{{restaurant-img model.id}}" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
      <h4><i class="glyphicon glyphicon-star"></i> Rating: {{model.rating}}</h4>
      <h4><i class="glyphicon glyphicon-usd"></i> Price: {{model.price}}</h4>
      <span class="text-muted"><i class="glyphicon glyphicon-tag"></i> {{model.cuisine}}</span>
   </div>

   <div class="col-md-9">
      <h3>{{t 'description'}}</h3>
      {{model.description}}

      <h3>{{t 'location'}}</h3>
      {{model.location}}

      <h3>{{t 'openingHours'}}</h3>
      <ul>
         <li>Opens/Closes: {{model.opens}} - {{model.closes}}</li>
         <li>
            Days opened:
            <ul>
               {{#each model.days as |day|}}
                  <li>{{capitalize (t (concat 'weekDays.' (weekday day)))}}</li>
               {{/each}}
            </ul>
         </li>
      </ul>

      <h3>{{t 'menu'}}</h3>
      <ul>
         {{#each model.menuItems as |item|}}
            <li>{{capitalize item.name}} <span class="text-muted">{{item.price}} €</span></li>
         {{/each}}
      </ul>
   </div>
</div>
import { helper } from '@ember/component/helper';
import { htmlSafe } from '@ember/template';

export function capitalize([input] /*, hash*/ ) {
    let r = input;

    if (typeof input === 'string') {
        r = input.capitalize();
    }

    if (typeof input === 'object' && typeof input.toString === 'function') {
        r = input.toString().capitalize();
    }

    return htmlSafe(r);
}

export default helper(capitalize);

Enlazando a un restaurante

Utiliza link-to para ir a la ruta 'detail', pasando como parámetro el restaurante. Ember extraerá automáticamente la popiedad 'id'.

// MODIFICA app/templates/restaurants/index.hbs
...
{{#each filteredRestaurants as |restaurant| }}
   <div class="media">
      <a class="media-left">
         <img class="media-object" src={{restaurant-img restaurant.id}} width="64" height="64">
      </a>
      <div class="media-body">
         <h4 class="media-heading">
            {{link-to restaurant.name 'restaurants.detail' restaurant}}
            <span class="label label-info">{{capitalize restaurant.cuisine}}</span>
         </h4>
         {{restaurant.description}}
      </div>
   </div>
{{/each}}
...

INPUT Helpers

Son la forma más fácil de crear controles de formulario comunes. Con ellos podemos crear estos elementos con una sintáxis casi idéntica al HTML tradicional.

 

La docu:

https://guides.emberjs.com/release/templates/input-helpers/

 

Un ejemplo básico de input con binding de propiedades:

https://ember-twiddle.com/a67797d151288c1c0324f4da7289ccc4

 h

 

Ejercicio por parejas (1)

Añade un input en el panel del buscador para que filtre buscando por nombre

Ejercicio por parejas (2)

Añade un botón en el panel del buscador para reiniciar los filtros de búsqueda

MIXINS

Son la forma más fácil de crear controles de formulario comunes. Con ellos podemos crear estos elementos con una sintáxis casi idéntica al HTML tradicional.

 

La docu:

https://guides.emberjs.com/release/templates/input-helpers/

 

Un ejemplo básico de input con binding de propiedades:

https://ember-twiddle.com/a67797d151288c1c0324f4da7289ccc4

 h

 

Ejercicio por parejas (3)

Añade un botón a cada restaurante para buscarlo en Google Maps:

  1. Crea un mixin con un action que genere una URL de GMaps a partir del nombre del restaurante.
  2. Inserta el mixin en los controladores del listado de restaurante y del detalle.
  3. Inserta un enlace en ambas templates (index y detail) que llame al action.

Pista: click aquí para buscar la documentación técnica

Oops! no tenemos controller para 'detail'. ¿Crees que es necesario crearlo?

acceptance tests

Son test a alto nivel, y comprueban una funcionalidad desde el punto de vista de un usuario.

  $ ember g acceptance-test restaurants

// tests/acceptance/restaurants-test.js

import { module, test } from 'qunit';
import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';

module('Acceptance | restaurants', function(hooks) {
  setupApplicationTest(hooks);

  test('visiting /restaurants', async function(assert) {
    await visit('/restaurants');

    assert.equal(currentURL(), '/restaurants');
  });
});

ember-cli nos crea un test básico

acceptance tests

Vamos a definir qué tiene que hacer nuestra funcionalidad

import { module, test } from 'qunit';
import { visit, currentURL, click, fillIn } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';

module('Acceptance | restaurants', function(hooks) {
  setupApplicationTest(hooks);

  test('visiting /restaurants', async function(assert) {
    await visit('/restaurants');

    assert.equal(currentURL(), '/restaurants');
  });

  test('should link to information about the company.', async function (assert) {
  });

  test('should show restaurants list at home page', async function(assert) {
  });

  test('should filter the list of restaurants by name.', async function (assert) {
  });

  test('should transition to the selected restaurant details', async function (assert) {
  });

});

Ejercicio

Cosas que necesitarás:

Ahora tienes que implementarlos ;)

this.element.querySelector('SELECTOR CSS')

this.element.querySelectorAll('SELECTOR CSS')

assert.ok() // 1 arg (evalúa true/false)

assert.equal() // 2 args (evalúa si son iguales)

import { visit, currentURL, click, fillIn } from '@ember/test-helpers';

¿Dudas?

 

juanantonio.gomez@beeva.com

 

beatriz.demiguel@beeva.com

¡¡GRACIAS!!

Made with Slides.com