@yoann-86/react_test_lab 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/install-node/action.yml +25 -0
- package/.github/workflows/build_test_deploy_react.yml +114 -0
- package/README.md +105 -0
- package/TEST_PLAN.md +94 -0
- package/babel.config.js +6 -0
- package/cypress/e2e/app-spec.cy.js +182 -0
- package/cypress/e2e/navigation.cy.js +101 -0
- package/cypress/fixtures/example.json +5 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +17 -0
- package/cypress.config.js +12 -0
- package/dist/App.css +108 -0
- package/dist/App.js +59 -0
- package/dist/App.test.js +225 -0
- package/dist/components/UserForm.js +188 -0
- package/dist/components/UserForm.test.js +291 -0
- package/dist/components/UsersList.js +38 -0
- package/dist/components/UsersList.test.js +134 -0
- package/dist/components/atomic/TextInput.js +42 -0
- package/dist/index.css +13 -0
- package/dist/index.js +23 -0
- package/dist/infra/api.js +27 -0
- package/dist/infra/api.test.js +54 -0
- package/dist/logo.svg +1 -0
- package/dist/reportWebVitals.js +26 -0
- package/dist/setupTests.js +3 -0
- package/dist/utils/module.js +25 -0
- package/dist/utils/module.test.js +78 -0
- package/dist/utils/validator.js +116 -0
- package/dist/utils/validator.test.js +324 -0
- package/jsdoc.config.json +10 -0
- package/package.json +62 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/repo.md +13 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: install-node
|
|
2
|
+
description: Install dependencies for build and test jobs
|
|
3
|
+
runs:
|
|
4
|
+
using: "composite"
|
|
5
|
+
steps:
|
|
6
|
+
- name: 🔧 Use Node.js 24.x
|
|
7
|
+
uses: actions/setup-node@v5
|
|
8
|
+
with:
|
|
9
|
+
node-version: 24.x
|
|
10
|
+
|
|
11
|
+
- name: 🔐 Enable Corepack
|
|
12
|
+
run: corepack enable
|
|
13
|
+
shell: bash
|
|
14
|
+
|
|
15
|
+
- name: 💾 Cache node_modules
|
|
16
|
+
id: cache-node-modules
|
|
17
|
+
uses: actions/cache@v4
|
|
18
|
+
with:
|
|
19
|
+
path: node_modules
|
|
20
|
+
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
21
|
+
|
|
22
|
+
- name: 📥 Install dependencies
|
|
23
|
+
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
|
24
|
+
run: pnpm install --frozen-lockfile
|
|
25
|
+
shell: bash
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
name: Build, Test and Deploy React Application
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: 🏗️ Build on Node.js 24.x
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
env:
|
|
14
|
+
REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: 👯♂️ Clone repository
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
- name: Install Node.js 24.x and pnpm
|
|
20
|
+
uses: ./.github/actions/install-node
|
|
21
|
+
- name: 📑 Generate JSDoc documentation
|
|
22
|
+
run: pnpm jsdoc
|
|
23
|
+
- name: 🧰 Build
|
|
24
|
+
id: build
|
|
25
|
+
run: pnpm run build --if-present
|
|
26
|
+
- name: 🗒️ Build report
|
|
27
|
+
if: always()
|
|
28
|
+
run: |
|
|
29
|
+
echo "Node.js version: $(node -v)"
|
|
30
|
+
if [[ ${{ steps.build.outcome }} == "success" ]]; then
|
|
31
|
+
echo "✅ Build succeeded"
|
|
32
|
+
else
|
|
33
|
+
echo "❌ Build failed"
|
|
34
|
+
fi
|
|
35
|
+
- name: 🔧 Setup pages
|
|
36
|
+
id: pages
|
|
37
|
+
uses: actions/configure-pages@v3
|
|
38
|
+
- name: 📤 Upload artifact for deployment
|
|
39
|
+
uses: actions/upload-pages-artifact@v3
|
|
40
|
+
with:
|
|
41
|
+
path: build
|
|
42
|
+
|
|
43
|
+
test:
|
|
44
|
+
name: 🧪 Test on Node.js 24.x
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- name: 👯♂️ Clone repository
|
|
49
|
+
uses: actions/checkout@v4
|
|
50
|
+
- name: Install Node.js 24.x and pnpm
|
|
51
|
+
uses: ./.github/actions/install-node
|
|
52
|
+
- name: 🧪 Integration Tests
|
|
53
|
+
id: int-test
|
|
54
|
+
run: pnpm test
|
|
55
|
+
- name: 🗒️ Test report
|
|
56
|
+
if: always()
|
|
57
|
+
run: |
|
|
58
|
+
echo "Node.js version: $(node -v)"
|
|
59
|
+
if [[ ${{ steps.int-test.outcome }} == "success" ]]; then
|
|
60
|
+
echo "✅ Integration Tests passed"
|
|
61
|
+
else
|
|
62
|
+
echo "❌ Integration Tests failed"
|
|
63
|
+
fi
|
|
64
|
+
- name: Upload coverage reports to Codecov
|
|
65
|
+
uses: codecov/codecov-action@v5
|
|
66
|
+
env:
|
|
67
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
68
|
+
e2e:
|
|
69
|
+
name: 🎭 End-to-End Tests
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
env:
|
|
72
|
+
REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}
|
|
73
|
+
|
|
74
|
+
steps:
|
|
75
|
+
- name: 👯♂️ Clone repository
|
|
76
|
+
uses: actions/checkout@v4
|
|
77
|
+
- name: Install Node.js 24.x and pnpm
|
|
78
|
+
uses: ./.github/actions/install-node
|
|
79
|
+
|
|
80
|
+
- name: 🔨 Build for E2E
|
|
81
|
+
run: PUBLIC_URL=/ pnpm run build
|
|
82
|
+
- name: 📦 Install Cypress binary manually
|
|
83
|
+
run: pnpm exec cypress install
|
|
84
|
+
- name: 🎭 Run e2e tests with Cypress
|
|
85
|
+
id: e2e-test
|
|
86
|
+
uses: cypress-io/github-action@v6
|
|
87
|
+
with:
|
|
88
|
+
install: false
|
|
89
|
+
start: npx serve -s build -l 3000
|
|
90
|
+
wait-on: "http://localhost:3000"
|
|
91
|
+
- name: 🗒️ E2E Test report
|
|
92
|
+
if: always()
|
|
93
|
+
run: |
|
|
94
|
+
echo "Node.js version: $(node -v)"
|
|
95
|
+
if [[ ${{ steps.e2e-test.outcome }} == "success" ]]; then
|
|
96
|
+
echo "✅ End-to-End Tests passed"
|
|
97
|
+
else
|
|
98
|
+
echo "❌ End-to-End Tests failed"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
deploy:
|
|
102
|
+
name: 🚀 Deploy to GitHub Pages
|
|
103
|
+
environment:
|
|
104
|
+
name: github-pages
|
|
105
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
106
|
+
runs-on: ubuntu-latest
|
|
107
|
+
permissions:
|
|
108
|
+
pages: write
|
|
109
|
+
id-token: write
|
|
110
|
+
needs: [build, test, e2e]
|
|
111
|
+
steps:
|
|
112
|
+
- name: 🚀 Deploy to GitHub Pages
|
|
113
|
+
id: deployment
|
|
114
|
+
uses: actions/deploy-pages@v4
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# React Test Lab
|
|
2
|
+
|
|
3
|
+
Une application React de démonstration avec tests unitaires, documentation JSDoc et CI/CD automatisée via GitHub Actions.
|
|
4
|
+
|
|
5
|
+
## Pré-requis
|
|
6
|
+
|
|
7
|
+
- **Node.js** 22.x ou supérieur
|
|
8
|
+
- **pnpm** (gestionnaire de paquets)
|
|
9
|
+
|
|
10
|
+
Pour installer pnpm :
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g pnpm
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Cloner le projet et installer les dépendances :
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone git@github.com:YoannAuroyYnov/react-test-lab.git
|
|
22
|
+
cd react_test_lab
|
|
23
|
+
pnpm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Exécuter l'application
|
|
27
|
+
|
|
28
|
+
### Mode développement
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm start
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
L'application s'ouvrira automatiquement dans votre navigateur à l'adresse [http://localhost:3000](http://localhost:3000).
|
|
35
|
+
|
|
36
|
+
### Build de production
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm build
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Les fichiers optimisés seront générés dans le dossier `build/`.
|
|
43
|
+
|
|
44
|
+
## Exécuter les tests
|
|
45
|
+
|
|
46
|
+
### Tests unitaires et d'intégration (Jest + Testing Library)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm test
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Le rapport de couverture sera disponible dans le dossier `coverage/`.
|
|
53
|
+
|
|
54
|
+
### Tests end-to-end (Cypress)
|
|
55
|
+
|
|
56
|
+
Dans un premier terminal, démarrez l'application :
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pnpm start
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Dans un second terminal, lancez Cypress en mode headless :
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx cypress run
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Vous pouvez aussi utiliser l'interface graphique :
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pnpm cypress
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Mocks utilisés dans les tests
|
|
75
|
+
|
|
76
|
+
- **Jest / Testing Library** : `axios` est mocké dans les tests (par ex. `App.test.js`) pour contrôler les réponses de l'API `/users` et déclencher les chemins heureux ou d'erreur sans requêtes réseau réelles.
|
|
77
|
+
- **Cypress** : les appels HTTP sont interceptés avec `cy.intercept` (ex. `cypress/e2e/navigation.cy.js`) pour stubber `GET /users` et `POST /users`, simuler un premier chargement vide ou prérempli, et forcer un retour `201` lors de la création d'utilisateur. Cela rend les parcours E2E déterministes et rapides.
|
|
78
|
+
|
|
79
|
+
## Documentation
|
|
80
|
+
|
|
81
|
+
Pour générer la documentation JSDoc :
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pnpm jsdoc
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
La documentation sera générée dans `public/docs/` et accessible à `http://localhost:300/docs` après le build.
|
|
88
|
+
|
|
89
|
+
## Déploiement
|
|
90
|
+
|
|
91
|
+
Le projet est automatiquement déployé sur GitHub Pages via GitHub Actions lors d'un push sur la branche `main`.
|
|
92
|
+
|
|
93
|
+
URL de déploiement : https://yoannauroyynov.github.io/react-test-lab/
|
|
94
|
+
|
|
95
|
+
## Configuration CI/CD - Note sur l'optimisation
|
|
96
|
+
|
|
97
|
+
**⚠️ Important :** La configuration CI/CD actuelle (parallélisation des jobs, caching pnpm, etc.) est explorée à titre **éducatif**. Sur un projet de cette taille, ces optimisations ne sont **pas pertinentes** en pratique.
|
|
98
|
+
|
|
99
|
+
**Raison :** Les temps incompressibles de démarrage des conteneurs Docker (cold start, allocation de ressources) sont bien supérieurs aux gains potentiels du caching (~30-60 secondes économisées vs ~10-30 secondes de démarrage). En d'autres termes, le surcoût du lancement des pods dépasse largement les bénéfices de l'optimisation.
|
|
100
|
+
|
|
101
|
+
Cette exploration reste utile pour :
|
|
102
|
+
|
|
103
|
+
- Comprendre les bonnes pratiques GitHub Actions
|
|
104
|
+
- Mettre en place une architecture scalable pour des projets plus volumineux
|
|
105
|
+
- Démontrer une approche professionnelle de la CI/CD
|
package/TEST_PLAN.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Documentation des Tests
|
|
2
|
+
|
|
3
|
+
## 📊 Vue d'ensemble
|
|
4
|
+
|
|
5
|
+
Ce projet implémente une suite complète de **119 tests** couvrant la validation de formulaire en deux niveaux :
|
|
6
|
+
|
|
7
|
+
- **Tests unitaires** : Logique métier du module `validator.js`
|
|
8
|
+
- **Tests d'intégration** : Interface React et interactions utilisateur
|
|
9
|
+
|
|
10
|
+
## 🎯 Couverture de Code
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
----------------|---------|----------|---------|---------|-------------------
|
|
14
|
+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
15
|
+
----------------|---------|----------|---------|---------|-------------------
|
|
16
|
+
All files | 100 | 93.75 | 100 | 100 |
|
|
17
|
+
src | 100 | 100 | 100 | 100 |
|
|
18
|
+
App.js | 100 | 100 | 100 | 100 |
|
|
19
|
+
src/components | 100 | 93.75 | 100 | 100 |
|
|
20
|
+
UserForm.jsx | 100 | 93.75 | 100 | 100 | 106
|
|
21
|
+
----------------|---------|----------|---------|---------|-------------------
|
|
22
|
+
|
|
23
|
+
Test Suites: 3 passed, 3 total
|
|
24
|
+
Tests: 119 passed, 119 total
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**✅ 100% de couverture** (Statements, Functions, Lines)
|
|
28
|
+
|
|
29
|
+
## 🧪 Tests Unitaires (validator.js) - 116 tests
|
|
30
|
+
|
|
31
|
+
### Fonctions testées
|
|
32
|
+
|
|
33
|
+
| Fonction | Tests | Couvre |
|
|
34
|
+
| --------------------- | ----- | --------------------------------------------------- |
|
|
35
|
+
| `validateAge()` | 9 | Majeur/mineur, dates futures, erreurs de paramètres |
|
|
36
|
+
| `validateZipCode()` | ~20 | Codes FR métropole/Corse/DOM-TOM, formats invalides |
|
|
37
|
+
| `validateIndentity()` | ~18 | Noms/prénoms, accents, injection SQL/XSS |
|
|
38
|
+
| `validateName()` | ~13 | Caractères spéciaux, chiffres, champs vides |
|
|
39
|
+
| `validateEmail()` | ~25 | Format RFC 5322, injections, domaines invalides |
|
|
40
|
+
|
|
41
|
+
### 🛡️ Sécurité - 30+ tests anti-injection
|
|
42
|
+
|
|
43
|
+
**Vecteurs testés** :
|
|
44
|
+
|
|
45
|
+
- SQL Injection : `'; DROP TABLE users;--`, `' OR '1'='1`
|
|
46
|
+
- XSS : `<script>alert('XSS')</script>`, `<img src=x onerror=...>`
|
|
47
|
+
- Path Traversal : `../../../etc/passwd`, `Anne\\root`
|
|
48
|
+
- Command Injection : `John; rm -rf /`
|
|
49
|
+
|
|
50
|
+
**Protection** : Rejet des caractères `<>:;/\@[]{}` dans noms/emails
|
|
51
|
+
|
|
52
|
+
## 🖥️ Tests d'Intégration (React) - 3 tests
|
|
53
|
+
|
|
54
|
+
### 1. Happy Path
|
|
55
|
+
|
|
56
|
+
Remplit le formulaire correctement, vérifie :
|
|
57
|
+
|
|
58
|
+
- ✅ Bouton disabled → enabled
|
|
59
|
+
- ✅ Sauvegarde dans localStorage
|
|
60
|
+
- ✅ Vidage des champs après soumission
|
|
61
|
+
|
|
62
|
+
### 2. Utilisateur Chaotique
|
|
63
|
+
|
|
64
|
+
Simule erreurs et corrections :
|
|
65
|
+
|
|
66
|
+
- ✅ Saisies invalides (chiffres, caractères spéciaux, injection SQL)
|
|
67
|
+
- ✅ Messages d'erreur affichés
|
|
68
|
+
- ✅ Corrections successives
|
|
69
|
+
- ✅ Validation finale
|
|
70
|
+
|
|
71
|
+
### 3. Counter (Sanity Check)
|
|
72
|
+
|
|
73
|
+
Test basique de fonctionnement
|
|
74
|
+
|
|
75
|
+
## 📝 Règles de Validation
|
|
76
|
+
|
|
77
|
+
| Champ | Règles |
|
|
78
|
+
| ------------------ | ---------------------------------------------------------------------- |
|
|
79
|
+
| **Prénom/Nom** | Obligatoire, pas de chiffres ni caractères spéciaux, accents autorisés |
|
|
80
|
+
| **Email** | Format valide (RFC 5322), présence de @ et domaine |
|
|
81
|
+
| **Date naissance** | Majeur (18+ ans), pas de date future |
|
|
82
|
+
| **Code postal** | 5 chiffres, codes français valides uniquement (01-95, 20xxx, 971-976) |
|
|
83
|
+
|
|
84
|
+
## 🚀 Commandes
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Tous les tests avec couverture
|
|
88
|
+
pnpm test
|
|
89
|
+
|
|
90
|
+
# Rapport HTML
|
|
91
|
+
open coverage/lcov-report/index.html
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
package/babel.config.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
describe("Home page spec", () => {
|
|
2
|
+
const person = {
|
|
3
|
+
firstname: "Yoann",
|
|
4
|
+
lastname: "Auroy",
|
|
5
|
+
email: "yoann.auroy@ynov.com",
|
|
6
|
+
birth: "1986-12-31",
|
|
7
|
+
city: "Paris",
|
|
8
|
+
zipCode: "75001",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
context("when users are already registered", () => {
|
|
12
|
+
const users = [
|
|
13
|
+
{ firstname: "Alice", lastname: "Smith", name: "Alice Smith" },
|
|
14
|
+
{ firstname: "Bob", lastname: "Johnson", name: "Bob Johnson" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
cy.intercept("GET", "/users", users);
|
|
19
|
+
cy.visit("/react-test-lab");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should count the number of registered users", () => {
|
|
23
|
+
cy.get("h2").should("have.text", "Il y a 2 utilisateurs enregistrés");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should display the list of registered users", () => {
|
|
27
|
+
cy.get("[data-testid=users-list]")
|
|
28
|
+
.children()
|
|
29
|
+
.should("have.length", 2)
|
|
30
|
+
.first()
|
|
31
|
+
.should("contain.text", "1 - Alice Smith");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should add a new user to the list after registration", () => {
|
|
35
|
+
cy.get("[data-testid=navigation-button]").click();
|
|
36
|
+
|
|
37
|
+
cy.get("[data-testid=firstname-input]")
|
|
38
|
+
.type(person.firstname)
|
|
39
|
+
.should("have.value", person.firstname);
|
|
40
|
+
|
|
41
|
+
cy.get("[data-testid=lastname-input]")
|
|
42
|
+
.type(person.lastname)
|
|
43
|
+
.should("have.value", person.lastname);
|
|
44
|
+
|
|
45
|
+
cy.get("[data-testid=email-input]")
|
|
46
|
+
.type(person.email)
|
|
47
|
+
.should("have.value", person.email);
|
|
48
|
+
|
|
49
|
+
cy.get("[data-testid=birth-input]")
|
|
50
|
+
.type(person.birth)
|
|
51
|
+
.should("have.value", person.birth);
|
|
52
|
+
|
|
53
|
+
cy.get("[data-testid=city-input]")
|
|
54
|
+
.type(person.city)
|
|
55
|
+
.should("have.value", person.city);
|
|
56
|
+
|
|
57
|
+
cy.get("[data-testid=zip-input]")
|
|
58
|
+
.type(person.zipCode)
|
|
59
|
+
.should("have.value", person.zipCode);
|
|
60
|
+
|
|
61
|
+
cy.get("[data-testid=submit-button]").click();
|
|
62
|
+
|
|
63
|
+
cy.get("[data-testid=users-list]")
|
|
64
|
+
.children()
|
|
65
|
+
.should("have.length", 3)
|
|
66
|
+
.last()
|
|
67
|
+
.should("contain.text", "3 - Yoann Auroy");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should display only the 5 most recent users", () => {
|
|
71
|
+
const moreUsers = [
|
|
72
|
+
{ firstname: "Charlie", lastname: "Brown", name: "Charlie Brown" },
|
|
73
|
+
{ firstname: "David", lastname: "Wilson", name: "David Wilson" },
|
|
74
|
+
{ firstname: "Eve", lastname: "Davis", name: "Eve Davis" },
|
|
75
|
+
{ firstname: "Frank", lastname: "Miller", name: "Frank Miller" },
|
|
76
|
+
];
|
|
77
|
+
cy.intercept("GET", "/users", [...users, ...moreUsers]);
|
|
78
|
+
cy.visit("/react-test-lab");
|
|
79
|
+
|
|
80
|
+
cy.get("[data-testid=users-list]").children().should("have.length", 5);
|
|
81
|
+
|
|
82
|
+
cy.get("[data-testid=users-list]")
|
|
83
|
+
.children()
|
|
84
|
+
.first()
|
|
85
|
+
.should("contain.text", "1 - Bob Johnson");
|
|
86
|
+
|
|
87
|
+
cy.get("[data-testid=users-list]")
|
|
88
|
+
.children()
|
|
89
|
+
.last()
|
|
90
|
+
.should("contain.text", "5 - Frank Miller");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
context("when no users are registered", () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
cy.visit("/react-test-lab/register");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should display a message when no users are registered", () => {
|
|
100
|
+
cy.visit("/react-test-lab");
|
|
101
|
+
cy.get("h2").should("have.text", "Il n'y a aucun utilisateur enregistré");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should register a new user", () => {
|
|
105
|
+
cy.get("[data-testid=firstname-input]")
|
|
106
|
+
.type(person.firstname)
|
|
107
|
+
.should("have.value", person.firstname);
|
|
108
|
+
|
|
109
|
+
cy.get("[data-testid=lastname-input]")
|
|
110
|
+
.type(person.lastname)
|
|
111
|
+
.should("have.value", person.lastname);
|
|
112
|
+
|
|
113
|
+
cy.get("[data-testid=email-input]")
|
|
114
|
+
.type(person.email)
|
|
115
|
+
.should("have.value", person.email);
|
|
116
|
+
|
|
117
|
+
cy.get("[data-testid=birth-input]")
|
|
118
|
+
.type(person.birth)
|
|
119
|
+
.should("have.value", person.birth);
|
|
120
|
+
|
|
121
|
+
cy.get("[data-testid=city-input]")
|
|
122
|
+
.type(person.city)
|
|
123
|
+
.should("have.value", person.city);
|
|
124
|
+
|
|
125
|
+
cy.get("[data-testid=zip-input]")
|
|
126
|
+
.type(person.zipCode)
|
|
127
|
+
.should("have.value", person.zipCode);
|
|
128
|
+
|
|
129
|
+
cy.get("[data-testid=submit-button]").click();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
context("when form inputs are invalid", () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
cy.visit("/react-test-lab/register");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should validate form inputs and disable submit button on errors", () => {
|
|
139
|
+
cy.get("[data-testid=firstname-input]")
|
|
140
|
+
.type("Yo4nn")
|
|
141
|
+
.should("have.value", "Yo4nn");
|
|
142
|
+
cy.get("[data-testid=firstname-error-text]").should(
|
|
143
|
+
"have.text",
|
|
144
|
+
"Les chiffres sont interdits",
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
cy.get("[data-testid=lastname-input]")
|
|
148
|
+
.type("; SELECT * FROM users;")
|
|
149
|
+
.should("have.value", "; SELECT * FROM users;");
|
|
150
|
+
cy.get("[data-testid=lastname-error-text]").should(
|
|
151
|
+
"have.text",
|
|
152
|
+
"Les caractères spéciaux sont interdits",
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
cy.get("[data-testid=email-input]")
|
|
156
|
+
.type("yoann.invalid_email.com")
|
|
157
|
+
.should("have.value", "yoann.invalid_email.com");
|
|
158
|
+
cy.get("[data-testid=email-error-text]").should(
|
|
159
|
+
"have.text",
|
|
160
|
+
"Le format de l'email est invalide",
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
cy.get("[data-testid=birth-input]")
|
|
164
|
+
.type("2020-01-01")
|
|
165
|
+
.should("have.value", "2020-01-01");
|
|
166
|
+
cy.get("[data-testid=birth-error-text]").should(
|
|
167
|
+
"have.text",
|
|
168
|
+
"Vous devez être majeur pour vous inscrire",
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
cy.get("[data-testid=zip-input]")
|
|
172
|
+
.type("7500")
|
|
173
|
+
.should("have.value", "7500");
|
|
174
|
+
cy.get("[data-testid=zip-error-text]").should(
|
|
175
|
+
"have.text",
|
|
176
|
+
"Le code postal doit comporter 5 chiffres",
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
cy.get("[data-testid=submit-button]").should("be.disabled");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
describe("Navigation spec", () => {
|
|
2
|
+
const person = {
|
|
3
|
+
name: "Yoann Auroy",
|
|
4
|
+
firstname: "Yoann",
|
|
5
|
+
lastname: "Auroy",
|
|
6
|
+
email: "yoann.auroy@ynov.com",
|
|
7
|
+
birth: "1986-12-31",
|
|
8
|
+
city: "Paris",
|
|
9
|
+
zipCode: "75001",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
context("when no user is registered", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
cy.intercept("GET", "/users", []);
|
|
15
|
+
cy.intercept("POST", "/users", {
|
|
16
|
+
statusCode: 201,
|
|
17
|
+
body: { ...person, id: 1 },
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should navigate on the nominal flow", () => {
|
|
22
|
+
cy.visit("/react-test-lab");
|
|
23
|
+
cy.get("h2").should("have.text", "Il n'y a aucun utilisateur enregistré");
|
|
24
|
+
cy.get("[data-testid=users-list]").children().should("have.length", 0);
|
|
25
|
+
|
|
26
|
+
cy.get("[data-testid=navigation-button]").click();
|
|
27
|
+
cy.url().should("include", "/register");
|
|
28
|
+
|
|
29
|
+
cy.get("[data-testid=firstname-input]")
|
|
30
|
+
.type(person.firstname)
|
|
31
|
+
.should("have.value", person.firstname);
|
|
32
|
+
|
|
33
|
+
cy.get("[data-testid=lastname-input]")
|
|
34
|
+
.type(person.lastname)
|
|
35
|
+
.should("have.value", person.lastname);
|
|
36
|
+
|
|
37
|
+
cy.get("[data-testid=email-input]")
|
|
38
|
+
.type(person.email)
|
|
39
|
+
.should("have.value", person.email);
|
|
40
|
+
|
|
41
|
+
cy.get("[data-testid=birth-input]")
|
|
42
|
+
.type(person.birth)
|
|
43
|
+
.should("have.value", person.birth);
|
|
44
|
+
|
|
45
|
+
cy.get("[data-testid=city-input]")
|
|
46
|
+
.type(person.city)
|
|
47
|
+
.should("have.value", person.city);
|
|
48
|
+
|
|
49
|
+
cy.get("[data-testid=zip-input]")
|
|
50
|
+
.type(person.zipCode)
|
|
51
|
+
.should("have.value", person.zipCode);
|
|
52
|
+
|
|
53
|
+
cy.get("[data-testid=submit-button]").click();
|
|
54
|
+
|
|
55
|
+
cy.url().should("include", "/react-test-lab");
|
|
56
|
+
|
|
57
|
+
cy.get("h2").should("have.text", "Il y a 1 utilisateur enregistré");
|
|
58
|
+
|
|
59
|
+
cy.get("[data-testid=users-list]")
|
|
60
|
+
.children()
|
|
61
|
+
.should("have.length", 1)
|
|
62
|
+
.first()
|
|
63
|
+
.should("contain.text", "1 - Yoann Auroy");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
context("when a user is already registered", () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
cy.intercept("GET", "/users", [person]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should not navigate on error flow", () => {
|
|
73
|
+
cy.visit("/react-test-lab");
|
|
74
|
+
cy.get("h2").should("have.text", "Il y a 1 utilisateur enregistré");
|
|
75
|
+
|
|
76
|
+
cy.get("[data-testid=navigation-button]").click();
|
|
77
|
+
cy.url().should("include", "/register");
|
|
78
|
+
|
|
79
|
+
cy.get("[data-testid=firstname-input]")
|
|
80
|
+
.type("; DROP TABLE users;")
|
|
81
|
+
.should("have.value", "; DROP TABLE users;");
|
|
82
|
+
cy.get("[data-testid=firstname-error-text]").should(
|
|
83
|
+
"have.text",
|
|
84
|
+
"Les caractères spéciaux sont interdits",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
cy.get("[data-testid=submit-button]").should("be.disabled");
|
|
88
|
+
|
|
89
|
+
cy.get("[data-testid=back-button]").click();
|
|
90
|
+
cy.url().should("include", "/react-test-lab");
|
|
91
|
+
|
|
92
|
+
cy.get("h2").should("have.text", "Il y a 1 utilisateur enregistré");
|
|
93
|
+
|
|
94
|
+
cy.get("[data-testid=users-list]")
|
|
95
|
+
.children()
|
|
96
|
+
.should("have.length", 1)
|
|
97
|
+
.first()
|
|
98
|
+
.should("contain.text", "1 - Yoann Auroy");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ***********************************************
|
|
2
|
+
// This example commands.js shows you how to
|
|
3
|
+
// create various custom commands and overwrite
|
|
4
|
+
// existing commands.
|
|
5
|
+
//
|
|
6
|
+
// For more comprehensive examples of custom
|
|
7
|
+
// commands please read more here:
|
|
8
|
+
// https://on.cypress.io/custom-commands
|
|
9
|
+
// ***********************************************
|
|
10
|
+
//
|
|
11
|
+
//
|
|
12
|
+
// -- This is a parent command --
|
|
13
|
+
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
14
|
+
//
|
|
15
|
+
//
|
|
16
|
+
// -- This is a child command --
|
|
17
|
+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
|
18
|
+
//
|
|
19
|
+
//
|
|
20
|
+
// -- This is a dual command --
|
|
21
|
+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
22
|
+
//
|
|
23
|
+
//
|
|
24
|
+
// -- This will overwrite an existing command --
|
|
25
|
+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ***********************************************************
|
|
2
|
+
// This example support/e2e.js is processed and
|
|
3
|
+
// loaded automatically before your test files.
|
|
4
|
+
//
|
|
5
|
+
// This is a great place to put global configuration and
|
|
6
|
+
// behavior that modifies Cypress.
|
|
7
|
+
//
|
|
8
|
+
// You can change the location of this file or turn off
|
|
9
|
+
// automatically serving support files with the
|
|
10
|
+
// 'supportFile' configuration option.
|
|
11
|
+
//
|
|
12
|
+
// You can read more here:
|
|
13
|
+
// https://on.cypress.io/configuration
|
|
14
|
+
// ***********************************************************
|
|
15
|
+
|
|
16
|
+
// Import commands.js using ES2015 syntax:
|
|
17
|
+
import './commands'
|