-
Notifications
You must be signed in to change notification settings - Fork 20
Tests
Le framework de test par défaut proposé par Ember.js est QUnit.
Nous estimons que ce framework est aujourd'hui dépassé et en retard en termes de communauté, fonctionnalités, intégrations support et évolution, par rapport à d'autres frameworks tels que Jasmine ou Mocha. C'est pourquoi nous avons fait le choix de le remplacer par ce dernier, considéré comme la référence dans l'écosystème JS depuis plusieurs années (un exploit dans le monde JS!).
Le remplacement de QUnit par Mocha s'est faite grâce au plugin ember-cli-mocha.
L'exécution des tests se fait via la commande ember test (ou ember t). Il est possible de rejouer automatiquement les tests à chaque changement via la commande ember test --serve.
Il est possible de jouer directement les tests (sans passer par ember test) lorsque l'application est lancée (via ember serve ou ember s). Pour ce faire, il faut accéder à l'URL http://localhost:4200/tests.
Pour lancer un sous-ensemble défini de tests, on peut utiliser l'option --filter, par exemple : ember test --filter="assessments".
Le filtrage peut se faire aussi directement au niveau des tests (plutôt que du CLI), grâce aux options Mocha skip et only.
Il est également possible de lancer les tests avec ember exam, commande strictement compatible avec ember test, avec quelques options supplémentaires disponible. Par exemple, il est possible de lancer ses tests en parallèle.
Voir la documentation d'ember-exam.
La commande ember exam est strictement compatible avec ember test.
En plus, elle permet de lancer d'autres options en plus de celle disponible avec ember test.
Par exemple, elle permet de lancer
Ember.js permet de concevoir 3 types de tests :
- unitaires : pour tout ce qui est computed properties, serializers, adapters, helpers, services
- intégration : pour vérifier le rendu et le comportement d'un composant
- acceptance : pour jouer des scénarios end-to-end (E2E) dans des conditions au plus proches du réel
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { setupTest } from 'ember-mocha';
describe('Unit | Component | feedback-panel', function () {
setupTest('component:feedback-panel', {});
describe('#isFormClosed', function () {
it('should return true by default', function () {
// given
const component = this.subject();
// when
const isFormClosed = component.get('isFormClosed');
// then
expect(isFormClosed).to.be.true;
});
it('should return true if status equals "FORM_CLOSED"', function () {
// given
const component = this.subject();
component.set('status', 'FORM_CLOSED');
// when
const isFormClosed = component.get('isFormClosed');
// then
expect(isFormClosed).to.be.true;
});
it('should return false if status is not equal to "FORM_CLOSED"', function () {
// given
const component = this.subject();
component.set('status', 'FORM_OPENED');
// when
const isFormClosed = component.get('isFormClosed');
// then
expect(isFormClosed).to.be.false;
});
});
});import Ember from 'ember';
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { setupComponentTest } from 'ember-mocha';
import wait from 'ember-test-helpers/wait';
import hbs from 'htmlbars-inline-precompile';
// Définition des classes CSS et autres constantes utilisées plusieurs fois
const LINK_VIEW = '.feedback-panel__view--link';
const FORM_VIEW = '.feedback-panel__view--form';
const MERCIX_VIEW = '.feedback-panel__view--mercix';
const OPEN_LINK = '.feedback-panel__open-link';
const BUTTON_SEND = '.feedback-panel__button--send';
const BUTTON_CANCEL = '.feedback-panel__button--cancel';
// Définition des fonctions utils
function expectLinkViewToBeVisible(component) {
expect(component.$(LINK_VIEW)).to.have.length(1);
expect(component.$(FORM_VIEW)).to.have.length(0);
expect(component.$(MERCIX_VIEW)).to.have.length(0);
}
function expectFormViewToBeVisible(component) {
expect(component.$(LINK_VIEW)).to.have.length(0);
expect(component.$(FORM_VIEW)).to.have.length(1);
expect(component.$(MERCIX_VIEW)).to.have.length(0);
}
function expectMercixViewToBeVisible(component) {
expect(component.$(LINK_VIEW)).to.have.length(0);
expect(component.$(FORM_VIEW)).to.have.length(0);
expect(component.$(MERCIX_VIEW)).to.have.length(1);
}
describe('Integration | Component | feedback-panel', function () {
setupComponentTest('feedback-panel', {
integration: true
});
describe('Default rendering', function () {
it('should display only the "link" view', function () {
// when
this.render(hbs`{{feedback-panel}}`);
// then
expectLinkViewToBeVisible(this);
});
});
describe('Link view', function () {
beforeEach(function () {
this.render(hbs`{{feedback-panel status='FORM_CLOSED'}}`);
});
it('should display only the "link" view', function () {
expectLinkViewToBeVisible(this);
});
it('the link label should be "Signaler un problème"', function () {
expect(this.$(OPEN_LINK).text()).to.contains('Signaler un problème');
});
it('clicking on the open link should hide the "link" view and display the "form" view', function () {
// when
this.$(OPEN_LINK).click();
// then
expectFormViewToBeVisible(this);
});
});
describe('Form view', function () {
// Exemple de stubbing de la méthode Ember.Model#save()
let isSaveMethodCalled = false;
const storeStub = Ember.Service.extend({
createRecord() {
return Object.create({
save() {
isSaveMethodCalled = true;
return Ember.RSVP.resolve();
}
});
}
});
beforeEach(function () {
// configure answer & cie. model object
const assessment = Ember.Object.extend({ id: 'assessment_id' }).create();
const challenge = Ember.Object.extend({ id: 'challenge_id' }).create();
const answer = Ember.Object.extend({ id: 'answer_id', assessment, challenge }).create();
// render component
this.set('answer', answer);
this.render(hbs`{{feedback-panel answer=answer status='FORM_OPENED'}}`);
// stub store service
this.register('service:store', storeStub);
this.inject.service('store', { as: 'store' });
isSaveMethodCalled = false;
});
it('should display only the "form" view', function () {
expectFormViewToBeVisible(this);
});
it('should contain email input field', function () {
const $email = this.$('input.feedback-panel__field--email');
expect($email).to.have.length(1);
expect($email.attr('placeholder')).to.equal('Votre email (optionnel)');
});
it('should contain content textarea field', function () {
const $password = this.$('textarea.feedback-panel__field--content');
expect($password).to.have.length(1);
expect($password.attr('placeholder')).to.equal('Votre message');
});
it('should contain "send" button with label "Envoyer" and placeholder "Votre email (optionnel)"', function () {
const $buttonSend = this.$(BUTTON_SEND);
expect($buttonSend).to.have.length(1);
expect($buttonSend.text()).to.equal('Envoyer');
});
it('should contain "cancel" button with label "Annuler" and placeholder "Votre message"', function () {
const $buttonCancel = this.$(BUTTON_CANCEL);
expect($buttonCancel).to.have.length(1);
expect($buttonCancel.text()).to.equal('Annuler');
});
it('clicking on "cancel" button should close the "form" view and and display the "link" view', function () {
// when
this.$(BUTTON_CANCEL).click();
// then
expectLinkViewToBeVisible(this);
});
it('clicking on "send" button should save the feedback into the store / API and display the "mercix" view', function () {
// given
const $content = this.$('.feedback-panel__field--content');
$content.val('Prêtes-moi ta plume, pour écrire un mot');
$content.change();
// when
this.$(BUTTON_SEND).click();
// then
return wait().then(() => {
expect(isSaveMethodCalled).to.be.true;
expectMercixViewToBeVisible(this);
});
});
});
describe('Mercix view', function () {
beforeEach(function () {
this.render(hbs`{{feedback-panel status='FORM_SUBMITTED'}}`);
});
it('should display only the "mercix" view', function () {
expectMercixViewToBeVisible(this);
});
});
describe('Error management', function () {
it('should display error if "content" is blank', function () {
// given
this.render(hbs`{{feedback-panel status='FORM_OPENED' content=' '}}`);
// when
this.$(BUTTON_SEND).click();
// then
expect(this.$('.alert')).to.have.length(1);
expectFormViewToBeVisible(this);
});
it('should display error if "email" is set but invalid', function () {
// given
this.render(hbs`{{feedback-panel status='FORM_OPENED' content='Lorem ipsum dolor sit amet' email='wrong_email'}}`);
// when
this.$(BUTTON_SEND).click();
expect(this.$('.alert')).to.have.length(1);
expectFormViewToBeVisible(this);
});
it('should not display error if "form" view (with error) was closed and re-opened', function () {
// given
this.render(hbs`{{feedback-panel status='FORM_OPENED' content=' '}}`);
this.$(BUTTON_SEND).click();
expect(this.$('.alert')).to.have.length(1);
// when
this.$(BUTTON_CANCEL).click();
this.$(OPEN_LINK).click();
// then
expect(this.$('.alert')).to.have.length(0);
});
});
});/* On déclare explicitement les imports (même pour Mocha) pour faciliter la vie des IDE / Linters */
import { describe, it, beforeEach, afterEach } from 'mocha';
import { expect } from 'chai';
import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';
/* Définition des méthodes utiles, si besoin */
function assertDisplayedUrl(actualUrl, expectedUrl) {
expect(actualUrl).to.equal(expectedUrl);
}
/* Définition des constantes, si besoin */
const INDEX_PAGE_URL = '/';
const INDEX_LANDING_TEXT_SELECTOR = '.first-page-hero__main-value-prop';
/* Description de la suite de tests */
describe('Acceptance | a1 - Accéder à la plateforme pour démarrer un test', function () {
/* Déclaration de l'instance de l'application qui sera lancée lors des tests */
let application;
/* Initialisation de l'application, de préférence dans un forEach pour avoir des tests le plus indépendants */
beforeEach(function () {
application = startApp();
});
/* Destruction de l'application */
afterEach(function () {
destroyApp(application);
});
/* Toutes les méthodes asynchrones de type ember-helper peuvent être jouées d'affilées dans un beforeEach */
beforeEach(function () {
visit(INDEX_PAGE_URL);
click(INDEX_LANDING_TEXT_SELECTOR);
});
it('a1.0 peut visiter /', function () {
assertDisplayedUrl(currentURL(), INDEX_PAGE_URL) {
});
it('a1.1 la landing page contient un pitch de présentation', function () {
const $landingText = findWithAssert(INDEX_LANDING_TEXT_SELECTOR).text();
expect($landingText).to.contains('Développez vos compétences numériques');
});
it('a1.2 Sur la landing page, un lien pointant vers la page projet est présent dans les valeurs pix', function(){
findWithAssert('.first-page-about a[href="/projet"]');
});
});