Nous avons vu comment créer une application Rails avec ReactJS en mode BDD dans l'article précédent. Ici nous allons voir comment intégrer les actions utilisateurs toujours en mode BDD mais en ajoutant de la TDD car il nous faut maintenant pouvoir ajouter, modifier et supprimer nos items et il est impensable de proposer de telles fonctionnalités sans tests.

Fonctionnalités de création

Je vais traiter la fonctionnalité de création d'un item ; je ne vais pas couvrir la modification ni la suppression car c'est le même concept que la création.

dans features/items.feature je rajoute les lignes suivantes

Scenario: The view should allow to create a new item
  Given I am on the items index
  When I click on "New"
  Then I should be on items new page
  When I fill in "title" with "Go to Apple store"
  And I fill in "description" with "Buy Iphone X"
  And I click on "Submit"
  Then I should be on items index page
  And I should see "Go to Apple store"

et dans features/step_definitions/items_steps.rb les nouvelles étapes dont nous avons besoin.

When(/^I click on "([^"]*)"$/) do |arg1|
  click_on(arg1)
end

When(/^I fill in "([^"]*)" with "([^"]*)"$/) do |arg1, arg2|
  fill_in(arg1, :with => arg2)
end

Then(/^I should be on ([^"]*) new page$/) do |arg1|
  visit send("new_"+"#{arg1}".singularize+"_path")
end

Then(/^I should be on ([^"]*) index page$/) do |arg1|
  visit send("#{arg1}_path")
end

Pour passer les étapes, je vais ajouter le code suivant.

Dans dans la fonction render du composant app/javascript/components/ItemList.js javascript <div> <a href="items/new">New</a> </div>

Ensuite je crée le fichier app/views/items/new.html.erb à vide et j'ajoute la méthode new dans le contrôleur app/controllers/items_controller.rb

Lancer cucumber nous permettrait de valider les tests jusqu'à ceux de la vue. Nous n'avons pas créé le formulaire et nous allons y remédier.

Le formulaire en ReactJs

Pour commencer, ajoutons les méthodes nécessaires au contrôleur.

# app/controllers/items_controller.rb
class ItemsController < ApplicationController

  def index
    @items = Item.all
  end

  def new
  end

  def create
    redirect_to items_path if Item.create!(item_params)
  end

  private
    def item_params
      params.permit(:title, :description, :is_done)
    end

end

Ensuite dans la vue app/views/items/new.html.erb, j'ajoute la ligne suivante <%= react_component('NewItem', nil, {prerender: true}) %>

Il nous reste à écrire le composant du formulaire.

// app/javascripts/components/NewItem.js
var React = require("react");

class NewItem extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      description: ""
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form action="/items" method="post">
        <label>
          Title :
          <input
            name="title"
            type="text"
            value={this.state.title}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Description :
          <input
            name="description"
            type="text"
            value={this.state.description}
            onChange={this.handleInputChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

NewItem.propTypes = {
  title: React.PropTypes.string,
  description: React.PropTypes.string
};

module.exports = NewItem

On peut alors lancer cucumber et voir tout passer au vert.

$ cucumber features/item.feature:16
Using the default profile...
Feature: Managing items
  As a user
  So that I can go to shopping 
  I want to see my shopping list

  Background:                        # features/item.feature:6
    Given the following items exist: # features/step_definitions/items_steps.rb:2
      | id | title  | description | is_done |
      | 1  | Item 1 | bla         | false   |
      | 2  | Item 2 | blabla      | false   |

  Scenario: The view should allow to create a new item # features/item.feature:16
    Given I am on the items index                      # features/step_definitions/items_steps.rb:9
    When I click on "New"                              # features/step_definitions/items_steps.rb:17
    Then I should be on items new page                 # features/step_definitions/items_steps.rb:25
    When I fill in "title" with "Go to Apple store"    # features/step_definitions/items_steps.rb:21
    And I fill in "description" with "Buy Iphone X"    # features/step_definitions/items_steps.rb:21
    And I click on "Submit"                            # features/step_definitions/items_steps.rb:17
    Then I should be on items index page               # features/step_definitions/items_steps.rb:29
    And I should see "Go to Apple store"               # features/step_definitions/items_steps.rb:13

1 scenario (1 passed)
9 steps (9 passed)
0m2.553s

Fantastique ! Maintenant il est temps de rentrer dans le détail.

Tests unitaires

Actuellement, si la demande client est validée, je peux gruger l'application en lui envoyant des données vides par exemple et il va me créer un enregistrement à vide ; ce qui n'est juste pas concevable !

Dans le fichier test/models/item_test.rb je rajoute les tests suivants.

# test/models/item_test.rb
require 'test_helper'

class ItemTest < ActiveSupport::TestCase
  def setup
    @parameters = { title: "title", description: "a super description" }
  end

  test "it should not be created without title" do
    @parameters.reject! {|k, v| k == :title}
    assert_raises (ActiveRecord::RecordInvalid){ Item.create!(@parameters) }
  end

  test "it should not be created without description" do
    @parameters.reject! {|k, v| k == :description}
    assert_raises (ActiveRecord::RecordInvalid) { Item.create!(@parameters) }
  end

  test "it should not be done at creation" do
    @parameters.merge!({ is_done: true })
    item = Item.create!(@parameters)
    assert_not item.is_done
  end

  test "it should be created with full informations" do
    item = Item.create!(@parameters)
    assert item.valid?
    assert_not item.is_done
  end
end

Si on lance le test via rails test nous avons le résultat suivant.

Finished in 0.047446s, 84.3060 runs/s, 105.3824 assertions/s.
4 runs, 5 assertions, 3 failures, 0 errors, 0 skips

Allons dans le modèle et pallions à ces problèmes.

# app/models/items.rb
class Item < ApplicationRecord
  validates_presence_of :title
  validates_presence_of :description
  before_create :init_attributes

  private

    def init_attributes
      self.is_done = false
    end
end

Je peux lance les tests :

$ rails test
Run options: --seed 38558

# Running:

....

Finished in 0.048274s, 82.8597 runs/s, 103.5746 assertions/s.
4 runs, 5 assertions, 0 failures, 0 errors, 0 skips

Utilisation de l'application

Tous les testes passent, c'est l'heure de l'utilisation ! Pour cela, il suffit de taper foreman start.

Ajoutons un nouvel item en cliquant sur le bouton New, renseignons les champs titre et description ; on envoie avec le bouton Submit et là ... ça ne marche pas !!

On se retrouve avec une erreur ActionController::InvalidAuthenticityToken.

Comme toute application Rails, notre serveur est protégé des attaques CSRF !

Il nous faut ajouter dans notre formulaire l'information authenticity_token. Pour cela, nous allons commencer par ajouter dans notre vue l'appelle au helper form_authenticity_token que nous met à disposition Rails.

# app/views/items/new.html.erb
<%= react_component('NewItem', {authenticity_token: form_authenticity_token}, {prerender: true}) %>

Il nous reste à prendre en compte ce paramètre dans notre composant ReactJs NewItem.js.

On l'ajoute dans les propTypes comme suit.

// app/javascript/components/NewItem.js

NewItem.propTypes = {
  title: React.PropTypes.string,
  description: React.PropTypes.string,
  authenticity_token: React.PropTypes.string,
};

Puis dans la méthode render, nous l'ajoutons au formulaire HTML.

// app/javascript/components/NewItem.js

  render() {
    return (
      <form action="/items" method="post">
        <input type="hidden" value={this.state.authenticity_token} name="authenticity_token" />

Maintenant, si nous lançons la commande foreman start et que nous créons un item, tout se déroule comme prévu ; il est bien ajouté à la liste et visible dans la vue index.

Mon prochain article va couvrir l'intégration de cette application que l'on peut appeler de traditionnelle dans Cordova ; pour cella, il va falloir la faire évoluer vers une application plus orientée API/client.

Publié dans les catégories suivantes

javascriptruby
comments powered by Disqus

Téléphone

+33 637 700 504

Adresse

Bordeaux, 33300
France