Évaluation 2 — Sujet

2025-2026 · tout le cours

Informations générales

Partie Contenu Points
Partie 1 Questions théoriques (5 questions) 10 points
Partie 2 Exercice de codage — Gestionnaire de Playlist 10 points
Total 20 points

Comment rendre votre copie

Un seul fichier

Vous rendez un seul fichier texte : le fichier reponse_NOM.txt fourni dans l’outillage.

  1. Ouvrez outillage/reponse_NOM.txt dans votre éditeur.
  2. Renommez-le avec votre nom de famille en majuscules : reponse_DUPONT.txt.
  3. Écrivez toutes vos réponses (théorie et code) entre les balises prévues.
  4. Déposez ce fichier sur tp.enst.fr.

Les balises

Le fichier contient des balises de ce type :

%%%DEBUT_Q1%%%

(votre réponse ici)

%%%FIN_Q1%%%
ImportantNe modifiez pas les balises

Ne modifiez pas, ne déplacez pas et ne supprimez pas les balises. Elles servent à retrouver chacune de vos réponses. Une balise abîmée ou manquante rend la réponse introuvable, et donc non notée.

Les balises attendues pour cette évaluation sont :

Balise Contenu attendu
%%%DEBUT_IDENTITE%%% … %%%FIN_IDENTITE%%% Vos nom et prénom
%%%DEBUT_Q1%%% … %%%FIN_Q1%%% Réponse à la question 1 (parties a, b, c)
%%%DEBUT_Q2%%% … %%%FIN_Q2%%% Réponse à la question 2 (parties a, b, c)
%%%DEBUT_Q3%%% … %%%FIN_Q3%%% Réponse à la question 3 (parties a, b, c)
%%%DEBUT_Q4%%% … %%%FIN_Q4%%% Réponse à la question 4 (parties a, b, c, d)
%%%DEBUT_Q5%%% … %%%FIN_Q5%%% Réponse à la question 5 (parties a, b, c, d)
%%%DEBUT_S1%%% … %%%FIN_S1%%% Code de la section 1 (JSON et catalogue)
%%%DEBUT_S2%%% … %%%FIN_S2%%% Code de la section 2 (Web Component)
%%%DEBUT_S3%%% … %%%FIN_S3%%% Code de la section 3 (recherche et durée totale)
%%%DEBUT_REMARQUES%%% … %%%FIN_REMARQUES%%% Remarques libres (facultatif)

Votre responsabilité

  • Enregistrez très régulièrement (Ctrl+S). Des copies ont déjà été perdues à quelques minutes de la fin.
  • Vérifiez avant l’envoi que toutes vos réponses sont bien dans le fichier, et bien entre les bonnes balises.
  • Tout ce qui se trouve en dehors de ce fichier ne sera pas corrigé : votre projet Astro sert uniquement à écrire et tester votre code, il n’est pas ramassé.
  • Vous pouvez déposer le fichier plusieurs fois : seul le dernier dépôt est conservé.

Mise en place (5 minutes)

Téléchargez l’archive outillage.tar.gz depuis la page Outillage et décompressez-la.

  1. Copiez le dossier outillage/projet_template en entier, renommez la copie mon_projet (à la souris, ou avec cp -R outillage/projet_template mon_projet — -R en majuscule).
  2. Dans un terminal : cd mon_projet puis npx astro dev
  3. Ouvrez http://localhost:4321/ dans le navigateur.
  4. Vos fichiers .astro vont dans src/pages/, vos fichiers .ts dans src/scripts/.

Le détail complet et la résolution de problèmes se trouvent dans outillage/README.html.


Partie 1 — Questions théoriques

Durée indicative : 1h30 — 5 questions, 10 points

Répondez dans le fichier reponse_NOM.txt, entre les balises %%%DEBUT_Q1%%% … %%%FIN_Q1%%%, etc. Répondez à chaque question de manière claire et argumentée.


Question 1 — JSON, sérialisation et limites de la désérialisation (2 points)

Considérez le code TypeScript suivant :

interface IAnimal {
    nom: string;
    age: number;
    crier(): string;
}

class Chat implements IAnimal {
    nom: string;
    age: number;

    constructor(nom: string, age: number) {
        this.nom = nom;
        this.age = age;
    }

    crier(): string {
        return "Miaou !";
    }

    ronronner(): string {
        return "Rrrr...";
    }
}

const monChat = new Chat("Felix", 3);
const json: string = JSON.stringify(monChat);
console.log(json);

const chatRestaure: IAnimal = JSON.parse(json);
console.log(chatRestaure.nom);
console.log(chatRestaure.crier());

a) Que retourne JSON.stringify(monChat) (quelle chaîne est affichée par le console.log) ? Quelles propriétés sont présentes dans la chaîne JSON produite et qu’est-ce qui n’y figure pas ? Expliquez pourquoi.

b) La ligne chatRestaure.crier() compile sans erreur. Pourtant, que se passe-t-il à l’exécution ? Expliquez pourquoi TypeScript ne détecte pas ce problème. Dans un projet réel qui reçoit des données d’une API distante via fetch, pourquoi cette limitation est-elle particulièrement dangereuse ?

c) Écrivez une fonction toChat(data: IAnimal): Chat qui crée une véritable instance de Chat à partir des données désérialisées, permettant d’appeler crier() et ronronner().


Question 2 — Génériques (1,5 points)

Considérez le code suivant :

class Boite<T> {
    private contenu: T[];

    constructor() {
        this.contenu = [];
    }

    ajouter(element: T): void {
        this.contenu.push(element);
    }

    retirer(): T | undefined {
        return this.contenu.pop();
    }
}

const boiteNombres = new Boite<number>();
boiteNombres.ajouter(42);
boiteNombres.ajouter(17);

boiteNombres.ajouter("test");
const valeur = boiteNombres.retirer();

a) Quelle erreur produit boiteNombres.ajouter("test") ? Expliquez comment les génériques assurent la sécurité des types. Quel est le type exact de valeur et pourquoi inclut-il undefined ?

b) Un collègue propose d’utiliser any[] au lieu de T[] pour « simplifier ». Sans réécrire toute la classe, donnez un exemple concret de deux lignes de code (un appel à ajouter puis une utilisation du résultat de retirer()) qui compilerait sans erreur avec any mais planterait à l’exécution. Expliquez en une phrase pourquoi les génériques empêchent ce scénario.

c) Si on crée const boiteMixte = new Boite<number | string>(), peut-on y ajouter à la fois des nombres et des chaînes ? Quel est alors le type de retour de retirer() et quelle vérification faudrait-il faire avant d’utiliser la valeur retournée ?


Question 3 — Exceptions et gestion d’erreurs (1,5 points)

Considérez le code suivant :

class ErreurAge extends Error {
    age: number;
    constructor(age: number) {
        super(`Âge invalide : ${age}`);
        this.name = "ErreurAge";
        this.age = age;
    }
}

class ErreurNom extends Error {
    constructor(nom: string) {
        super(`Nom invalide : "${nom}"`);
        this.name = "ErreurNom";
    }
}

function creerUtilisateur(nom: string, age: number): { nom: string; age: number } {
    if (nom.length < 2) {
        throw new ErreurNom(nom);
    }
    if (age < 0 || age > 150) {
        throw new ErreurAge(age);
    }
    return { nom, age };
}

try {
    const u1 = creerUtilisateur("Alice", 25);
    console.log("A:", u1.nom);
    const u2 = creerUtilisateur("B", 30);
    console.log("B:", u2.nom);
    const u3 = creerUtilisateur("Charlie", -5);
    console.log("C:", u3.nom);
} catch (e: unknown) {
    if (e instanceof ErreurNom) {
        console.log("Erreur nom:", e.message);
    } else if (e instanceof ErreurAge) {
        console.log("Erreur age:", e.message, e.age);
    } else if (e instanceof Error) {
        console.log("Erreur:", e.message);
    }
} finally {
    console.log("Fin");
}

a) Tracez l’exécution : quelles lignes sont affichées dans la console, et dans quel ordre ? Pourquoi la création de u3 n’est-elle jamais atteinte ?

b) Pourquoi l’ordre des if / else if est-il important dans le bloc catch ? Que se passerait-il si on testait e instanceof Error en premier ? (Indice : pensez à l’héritage.)

c) Un collègue modifie le code pour mettre chaque appel à creerUtilisateur dans son propre bloc try/catch séparé. Quel avantage concret cela apporte-t-il par rapport au bloc try unique englobant les trois appels ?


Question 4 — Async/Await et Promesses (2,5 points)

Considérez le code suivant :

function attendre(ms: number): Promise<string> {
    return new Promise((resolve, reject) => {
        if (ms < 0) {
            reject(new Error("Durée négative"));
        }
        setTimeout(() => {
            resolve(`Terminé après ${ms}ms`);
        }, ms);
    });
}

async function scenario() {
    console.log("1. Début");

    const resultat = await attendre(1000);
    console.log("2.", resultat);

    try {
        const erreur = await attendre(-1);
        console.log("3.", erreur);
    } catch (e) {
        if (e instanceof Error) {
            console.log("4.", e.message);
        }
    }

    console.log("5. Fin");
}

console.log("A. Avant");
scenario();
console.log("B. Après");

a) Dans quel ordre apparaissent les messages dans la console ? Expliquez pourquoi "B. Après" s’affiche avant "2." alors qu’il apparaît après l’appel à scenario() dans le code.

b) Quel est le type de retour de attendre(1000) ? Quel est le type de resultat après le await ? Expliquez la transformation que await opère sur une promesse.

c) Si on remplaçait scenario(); par await scenario(); (en enveloppant le code de niveau supérieur dans une fonction async), comment l’ordre d’affichage changerait-il ? Justifiez.

d) Réécrivez les deux lignes const resultat = await attendre(1000); et console.log("2.", resultat); en version équivalente utilisant .then() au lieu de await.


Question 5 — CORS, Web Components et Formulaires (2,5 points)

Partie A — CORS (1 point)

Un étudiant développe une application Astro. Dans le frontmatter (---), il fait await fetch("https://api.exemple.com/data") et tout fonctionne. Il copie la même ligne dans un <script> côté client et obtient une erreur CORS dans la console du navigateur.

a) Expliquez pourquoi le même appel fetch fonctionne à un endroit mais pas à l’autre. Où s’exécute chacun des deux codes ?

b) Proposez deux solutions pour que le client puisse quand même accéder aux données. L’une de ces solutions doit utiliser la technique data-* + JSON.stringify vue en cours.

Partie B — Web Components et Formulaires (1,5 points)

Considérez le Web Component suivant :

class AlerteMessage extends HTMLElement {
    static get observedAttributes(): string[] {
        return ["message"];
    }

    connectedCallback() {
        this.render();
    }

    attributeChangedCallback(name: string, oldValue: string, newValue: string) {
        this.render();
    }

    render() {
        const msg = this.getAttribute("message") || "Aucun message";
        this.innerHTML = `<div style="border:2px solid red; padding:10px;">${msg}</div>`;
    }
}

customElements.define("alerte-message", AlerteMessage);

Et ce formulaire :

const form = document.getElementById("alertForm") as HTMLFormElement;
const input = document.getElementById("msgInput") as HTMLInputElement;
const alerte = document.querySelector("alerte-message") as AlerteMessage;

form.addEventListener("submit", function(event: Event) {
    alerte.setAttribute("message", input.value);
    console.log(this);
});

c) Quand le formulaire est soumis, l’alerte clignote brièvement puis la page se recharge et revient à son état initial. Identifiez le problème et proposez la correction (une seule ligne à ajouter, et à quel endroit).

d) Dans le gestionnaire submit, console.log(this) affiche l’élément <form>, pas l’objet global. Si on remplace function(event: Event) { ... } par une fonction fléchée (event: Event) => { ... }, que changerait la valeur de this et pourquoi ?

Fin de la Partie 1


Partie 2 — Exercice de codage

Durée indicative : 1h30 — 10 points

Contexte

Vous travaillez sur un gestionnaire de playlist musicale. On vous fournit un catalogue de pistes musicales sous forme de chaîne JSON dans un module TypeScript. Votre mission est de créer une page web interactive qui parse ces données, les affiche, utilise un Web Component personnalisé, et propose un formulaire de recherche.

Travaillez dans votre projet mon_projet pour écrire et tester votre code, puis recopiez le code de chaque section dans le fichier reponse_NOM.txt, entre les balises %%%DEBUT_S1%%%, %%%DEBUT_S2%%% et %%%DEBUT_S3%%%.

NoteUn code partiel rapporte des points

Rendez toujours ce que vous avez écrit, même incomplet. Les trois sections sont largement indépendantes les unes des autres.

Organisation des fichiers dans votre projet brouillon

mon_projet/
└── src/
    ├── pages/
    │   └── index.astro            ← à remplacer par le fichier fourni ci-dessous
    └── scripts/
        ├── catalogueMusique.ts    ← à créer avec le contenu fourni ci-dessous
        └── app.ts                 ← à remplacer par le squelette fourni ci-dessous

Aide-mémoire

Voici quelques méthodes et rappels utiles pour cet exercice :

.join(separateur) — Concaténer un tableau en une chaîne

La méthode .join() transforme un tableau en une seule chaîne de caractères, en insérant un séparateur entre chaque élément :

const fruits: string[] = ["pomme", "banane", "orange"];
console.log(fruits.join(", "));   // "pomme, banane, orange"

.slice(debut, fin) — Extraire une portion de tableau

La méthode .slice() retourne un nouveau tableau contenant les éléments de l’index debut jusqu’à l’index fin (non inclus) :

const nombres: number[] = [10, 20, 30, 40, 50];
const premiers: number[] = nombres.slice(0, 3);  // [10, 20, 30]

JSON.parse() et JSON.stringify()

// Désérialisation : chaîne JSON → objet JavaScript
const data: MonType[] = JSON.parse(chaineJSON);

// Sérialisation : objet JavaScript → chaîne JSON
const json: string = JSON.stringify(monObjet);

Créer un Web Component minimal

class MonElement extends HTMLElement {
    static get observedAttributes(): string[] {
        return ["mon-attribut"];
    }
    connectedCallback() { this.render(); }
    attributeChangedCallback() { this.render(); }
    render() {
        this.innerHTML = `<p>${this.getAttribute("mon-attribut")}</p>`;
    }
}
customElements.define("mon-element", MonElement);

event.preventDefault() — Empêcher le comportement par défaut

form.addEventListener("submit", (event: Event) => {
    event.preventDefault();  // empêche le rechargement de la page
    // ... traitement du formulaire
});

Fichier fourni 1 — src/scripts/catalogueMusique.ts

Créez ce fichier et recopiez-y ce contenu sans le modifier.

export interface Piste {
    titre: string;
    artiste: string;
    album: string;
    duree: number;      // en secondes
    genre: string;
    note: number;       // entre 0 et 5
}

// Données fournies sous forme de chaîne JSON (simulant une réponse d'API)
export const pistesJSON: string = `[
    {"titre":"Bohemian Rhapsody","artiste":"Queen","album":"A Night at the Opera","duree":354,"genre":"Rock","note":5},
    {"titre":"Imagine","artiste":"John Lennon","album":"Imagine","duree":187,"genre":"Pop","note":4.5},
    {"titre":"Billie Jean","artiste":"Michael Jackson","album":"Thriller","duree":294,"genre":"Pop","note":4.8},
    {"titre":"Smells Like Teen Spirit","artiste":"Nirvana","album":"Nevermind","duree":301,"genre":"Rock","note":4.7},
    {"titre":"Like a Rolling Stone","artiste":"Bob Dylan","album":"Highway 61 Revisited","duree":369,"genre":"Folk","note":4.3},
    {"titre":"One","artiste":"U2","album":"Achtung Baby","duree":276,"genre":"Rock","note":4.1},
    {"titre":"Lose Yourself","artiste":"Eminem","album":"8 Mile","duree":326,"genre":"Rap","note":4.6},
    {"titre":"Hey Jude","artiste":"The Beatles","album":"Single","duree":431,"genre":"Pop","note":4.9},
    {"titre":"Redemption Song","artiste":"Bob Marley","album":"Uprising","duree":227,"genre":"Reggae","note":4.4},
    {"titre":"Purple Rain","artiste":"Prince","album":"Purple Rain","duree":520,"genre":"Pop","note":4.7}
]`;

// Données invalides pour tester la gestion d'erreurs (JSON volontairement mal formé)
export const pistesJSON_invalide: string = `[
    {"titre":"Test","artiste":"Artiste", duree: "pas un nombre"}
`;

export const formatDuree = (secondes: number): string => {
    const min = Math.floor(secondes / 60);
    const sec = secondes % 60;
    return `${min}:${sec.toString().padStart(2, "0")}`;
};

Fichier fourni 2 — src/pages/index.astro

Remplacez le contenu du fichier existant par celui-ci.

---

---
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Playlist WebDev101</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 900px;
            margin: 20px auto;
            padding: 0 20px;
        }
        h1 { color: #2c3e50; }
        h2 { color: #34495e; border-bottom: 2px solid #e74c3c; padding-bottom: 5px; }
        .resultat {
            background-color: #f9f9f9;
            border: 1px solid #ddd;
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 10px 0;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #e74c3c;
            color: white;
        }
        tr:nth-child(even) { background-color: #f2f2f2; }
        .top { color: gold; font-weight: bold; }
        .normal { color: #333; }
        .erreur { color: red; font-style: italic; }
        piste-card {
            display: block;
            border: 1px solid #ccc;
            border-radius: 8px;
            padding: 10px;
            margin: 5px 0;
            background: #fafafa;
        }
        form { margin: 10px 0; }
        form label { margin-right: 10px; }
        form input[type="text"] { padding: 5px; margin-right: 10px; }
        form button {
            padding: 5px 15px;
            background-color: #e74c3c;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>🎵 Playlist WebDev101</h1>

    <h2>1. Catalogue (JSON → tableau)</h2>
    <div id="catalogue" class="resultat"></div>

    <h2>2. Web Component &lt;piste-card&gt;</h2>
    <div id="composants" class="resultat"></div>

    <h2>3. Recherche et statistiques</h2>
    <form id="searchForm">
        <label for="searchInput">Artiste :</label>
        <input type="text" id="searchInput" placeholder="Nom de l'artiste..." />
        <button type="submit">Rechercher</button>
    </form>
    <div id="recherche" class="resultat"></div>
    <div id="statistiques" class="resultat"></div>

    <script src="../scripts/app.ts"></script>
</body>
</html>

Fichier fourni 3 — src/scripts/app.ts (squelette à compléter)

C’est ce fichier que vous devez compléter. Remplacez le contenu du fichier existant par ce squelette. Chaque section est repérée par un commentaire // TODO.

// TODO 1 : Importez depuis le module catalogueMusique.ts :
// - l'interface Piste
// - la chaîne pistesJSON
// - la chaîne pistesJSON_invalide
// - la fonction formatDuree


// ============================================
// SECTION 1 : Désérialisation JSON et affichage (3,5 points)
// ============================================
// TODO 2 : Créez une fonction fléchée "parsePistes" qui prend une chaîne JSON
// en paramètre et retourne un tableau de Piste.
// Cette fonction doit utiliser try/catch :
// - dans le try : parser le JSON avec JSON.parse() et retourner le résultat
// - dans le catch : afficher un message d'erreur dans la console et retourner un tableau vide
//
// Testez d'abord avec pistesJSON_invalide pour vérifier que l'erreur est bien gérée,
// puis parsez pistesJSON pour obtenir le tableau de pistes.


// TODO 3 : Créez une fonction fléchée "creerLigneTableau" qui prend une Piste
// en paramètre et retourne une chaîne HTML représentant une ligne de tableau <tr>
// avec les colonnes : titre, artiste, album, durée (formatée avec formatDuree), genre, note.
// La ligne doit avoir la classe CSS "top" si la note >= 4.7, sinon "normal".
//
// Utilisez map avec creerLigneTableau pour transformer le tableau de pistes
// en lignes HTML, puis affichez le résultat dans le div #catalogue sous forme
// de tableau HTML complet avec les en-têtes.
// Rappel : utilisez .join("") pour concaténer les lignes du tableau.
const catalogueDiv = document.getElementById("catalogue") as HTMLDivElement;


// ============================================
// SECTION 2 : Web Component <piste-card> (3 points)
// ============================================
// TODO 4 : Créez un Web Component "piste-card" (classe PisteCard qui étend HTMLElement).
// Ce composant doit :
// - observer les attributs "titre" et "artiste" (via observedAttributes)
// - implémenter connectedCallback() qui appelle render()
// - implémenter attributeChangedCallback() qui appelle render()
// - implémenter render() qui affiche :
//   <strong>TITRE</strong> — <em>ARTISTE</em>
//   (en utilisant getAttribute("titre") et getAttribute("artiste"))
//
// Enregistrez le composant avec customElements.define("piste-card", PisteCard).
//
// Puis utilisez forEach sur les 5 premières pistes (utilisez .slice(0, 5))
// pour créer dynamiquement des éléments <piste-card> et les ajouter au div #composants.
// Pour chaque piste, créez l'élément avec document.createElement("piste-card"),
// définissez les attributs "titre" et "artiste" avec setAttribute(), puis ajoutez-le au div.
const composantsDiv = document.getElementById("composants") as HTMLDivElement;


// ============================================
// SECTION 3 : Recherche par artiste et durée totale (3,5 points)
// ============================================
// TODO 5 : Récupérez le formulaire #searchForm et l'input #searchInput.
// Ajoutez un gestionnaire d'événement "submit" sur le formulaire.
// N'oubliez pas d'appeler event.preventDefault() pour empêcher le rechargement de la page !
//
// Dans le gestionnaire :
// - récupérez la valeur saisie dans l'input
// - utilisez "filter" pour trouver toutes les pistes dont le nom de l'artiste
//   contient la valeur recherchée (insensible à la casse, utilisez .toLowerCase())
// - si aucune piste trouvée, affichez "Aucune piste trouvée pour cet artiste"
//   avec la classe CSS "erreur"
// - sinon, affichez les résultats sous forme de liste <ul><li>
//   Chaque puce : "Titre — Artiste (Album, durée)"
const rechercheDiv = document.getElementById("recherche") as HTMLDivElement;


// TODO 6 : Calculez la durée totale de toutes les pistes avec "reduce".
// Affichez le résultat dans le div #statistiques, formaté avec formatDuree :
// "Durée totale de la playlist : X:XX"
const statsDiv = document.getElementById("statistiques") as HTMLDivElement;

Travail demandé — détail des sections

Section 1 — Désérialisation JSON et affichage du catalogue (3,5 points) → %%%DEBUT_S1%%%

  • Importez depuis le module catalogueMusique.ts : l’interface Piste, les chaînes pistesJSON et pistesJSON_invalide, et la fonction formatDuree
  • Créez une fonction fléchée parsePistes qui prend une chaîne JSON en paramètre et retourne un tableau de Piste. Utilisez try/catch : dans le try, parsez le JSON et retournez le résultat ; dans le catch, affichez l’erreur dans la console et retournez un tableau vide
  • Testez d’abord avec pistesJSON_invalide pour vérifier la gestion d’erreurs, puis parsez pistesJSON
  • Créez une fonction fléchée creerLigneTableau qui prend une Piste et retourne une chaîne HTML <tr> avec les colonnes : titre, artiste, album, durée (formatée avec formatDuree), genre, note. La ligne doit avoir la classe CSS "top" si la note ≥ 4.7, sinon "normal"
  • Utilisez map pour transformer le tableau de pistes en lignes HTML et affichez dans le div#catalogue sous forme de tableau complet
Note

Recopiez la ligne d’import dans la balise %%%DEBUT_S1%%% : elle fait partie de cette section.

Section 2 — Web Component <piste-card> (3 points) → %%%DEBUT_S2%%%

  • Créez un Web Component PisteCard (classe héritant de HTMLElement) qui observe les attributs "titre" et "artiste" (via observedAttributes)
  • Implémentez connectedCallback() et attributeChangedCallback() qui appellent render()
  • render() affiche : <strong>TITRE</strong> — <em>ARTISTE</em> (en utilisant getAttribute)
  • Enregistrez le composant avec customElements.define("piste-card", PisteCard)
  • Utilisez forEach sur les 5 premières pistes (.slice(0, 5)) pour créer dynamiquement des éléments <piste-card> avec document.createElement, définissez les attributs et ajoutez-les au div#composants

Section 3 — Recherche par artiste et durée totale (3,5 points) → %%%DEBUT_S3%%%

  • Récupérez le formulaire #searchForm et l’input #searchInput
  • Ajoutez un gestionnaire d’événement submit avec event.preventDefault()
  • Utilisez filter pour trouver les pistes dont l’artiste contient la valeur recherchée (insensible à la casse avec .toLowerCase())
  • Si aucun résultat, affichez "Aucune piste trouvée pour cet artiste" avec la classe CSS "erreur"
  • Sinon, affichez les résultats sous forme de liste <ul><li> : "Titre — Artiste (Album, durée)"
  • Calculez la durée totale de toutes les pistes avec reduce et affichez-la dans le div#statistiques formatée avec formatDuree

Fin de la Partie 2


Rappel du fichier de réponses

Le fichier reponse_NOM.txt fourni dans l’outillage a la structure suivante. En cas de perte, vous pouvez le recréer à l’identique.

%%%DEBUT_IDENTITE%%%
NOM :
PRENOM :
%%%FIN_IDENTITE%%%

%%%DEBUT_Q1%%%
a)
b)
c)
%%%FIN_Q1%%%

%%%DEBUT_Q2%%%
...
%%%FIN_Q2%%%

%%%DEBUT_Q3%%%
...
%%%FIN_Q3%%%

%%%DEBUT_Q4%%%
...
%%%FIN_Q4%%%

%%%DEBUT_Q5%%%
...
%%%FIN_Q5%%%

%%%DEBUT_S1%%%
(code de la section 1)
%%%FIN_S1%%%

%%%DEBUT_S2%%%
(code de la section 2)
%%%FIN_S2%%%

%%%DEBUT_S3%%%
(code de la section 3)
%%%FIN_S3%%%

%%%DEBUT_REMARQUES%%%
(facultatif)
%%%FIN_REMARQUES%%%

Bon courage !

👉 Corrigé de l’évaluation 2