Maison > interface Web > js tutoriel > le corps du texte

Apprendre le TDD par la pratique : marquage des membres dans l'éditeur de texte enrichi d'Umbraco

Barbara Streisand
Libérer: 2024-10-08 06:21:01
original
808 Les gens l'ont consulté

Learning TDD by doing: Tagging members in Umbraco

Dans le système que je construis, j'ai besoin de pouvoir mentionner les membres d'Umbraco dans le texte du site Web. Pour ce faire, je dois créer une extension pour l'éditeur de texte enrichi d'Umbraco : TinyMCE.

Contexte

En tant qu'éditeur de contenu, je souhaite identifier les membres dans un message ou un article afin qu'ils soient informés du nouveau contenu les concernant.

J'ai regardé des implémentations similaires, comme dans Slack ou sur X. Slack utilise une balise html spéciale pour les mentions lors de l'écriture, mais envoie ensuite les données au backend avec un jeton avec un format spécifique. J'ai décidé d'adopter une approche similaire, mais pour l'instant, oubliez l'étape de traduction. En contenu, une mention ressemblera à ceci :


<mention user-id="1324" class="mceNonEditable">@D_Inventor</mention>


Copier après la connexion

Exploration initiale

Avant de commencer à construire, je cherchais des moyens de me connecter à TinyMCE à Umbraco. C'est l'une des choses que j'aime le moins étendre dans le backoffice d'Umbraco. Cependant, je l'ai déjà fait et j'ai trouvé plus facile d'étendre l'éditeur si je crée un décorateur sur tinyMceService d'Umbraco dans AngularJS. Dans la documentation de TinyMCE, j'ai trouvé une fonctionnalité appelée « autoCompleters », qui faisait exactement ce dont j'avais besoin, ce qui me permettait de me connecter à l'éditeur. Mon code initial (sans aucun test encore), ressemblait à ceci :


rtedecorator.$inject = ["$delegate"];
export function rtedecorator($delegate: any) {
  const original = $delegate.initializeEditor;

  $delegate.initializeEditor = function (args: any) {
    original.apply($delegate, arguments);

    args.editor.contentStyles.push("mention { background-color: #f7f3c1; }");
    args.editor.ui.registry.addAutocompleter("mentions", {
      trigger: "@",
      fetch: (
        pattern: string,
        maxResults: number,
        _fetchOptions: Record<string, unknown>
      ): Promise<IMceAutocompleteItem[]>
        // TODO: fetch from backend
        => Promise.resolve([{ type: "autocompleteitem", value: "1234", text: "D_Inventor" }]),
      onAction: (api: any, rng: Range, value: string): void => {
        // TODO: business logic
        api.hide();
      },
    });
  };

  return $delegate;
}


Copier après la connexion

J'utilise vite et typescript dans ce projet, mais je n'ai installé aucun type pour TinyMCE. Pour l'instant, je vais garder le any et essayer d'éviter TinyMCE autant que possible.

Construire avec TDD

J'ai décidé d'utiliser Jest pour les tests. J'ai trouvé un démarrage facile et j'ai rapidement réussi à faire fonctionner quelque chose.

✅ Success
I learned a new tool for unit testing in frontend code. I succesfully applied the tool to write a frontend with unit tests

J'ai écrit mon premier test :

mention-manager.test.ts


describe("MentionsManager.fetch", () => {
  let sut: MentionsManager;
  let items: IMention[];

  beforeEach(() => {
    items = [];
    sut = new MentionsManager();
  });

  test("should be able to fetch one result", async () => {
    items.push({ userId: "1234", userName: "D_Inventor" });
    const result = await sut.fetch(1);
    expect(result).toHaveLength(1);
  });
});


Copier après la connexion

J'ai été quelque peu surpris par la rigueur du compilateur TypeScript. Travailler par étapes ici signifiait vraiment ne rien ajouter que vous n’utilisiez pas encore. Par exemple, je voulais ajouter une référence à "UI", parce que je savais que j'allais l'utiliser plus tard, mais je ne pouvais pas réellement compiler MentionsManager tant que j'avais utilisé tout ce que j'avais mis dans le constructeur.

Après quelques tours de rouge, vert et refactor, je me suis retrouvé avec ces tests :

mention-manager.test.ts


describe("MentionsManager.fetch", () => {
  let sut: MentionsManager;
  let items: IMention[];

  beforeEach(() => {
    items = [];
    sut = new MentionsManager(() => Promise.resolve(items));
  });

  test("should be able to fetch one result", async () => {
    items.push({ userId: "1234", userName: "D_Inventor" });
    const result = await sut.fetch(1);
    expect(result).toHaveLength(1);
  });

  test("should be able to fetch empty result", async () => {
    const result = await sut.fetch(1);
    expect(result).toHaveLength(0);
  });

  test("should be able to fetch many results", async () => {
    items.push({ userId: "1324", userName: "D_Inventor" }, { userId: "3456", userName: "D_Inventor2" });
    const result = await sut.fetch(2);
    expect(result).toHaveLength(2);
  });

  test("should return empty list upon error", () => {
    const sut = new MentionsManager(() => {
      throw new Error("Something went wrong while fetching");
    }, {} as IMentionsUI);
    return expect(sut.fetch(1)).resolves.toHaveLength(0);
  });
});


Copier après la connexion

Avec cette logique en place, je pourrais récupérer les mentions de n'importe quelle source et les afficher dans le RTE via le hook 'fetch'.
J'ai utilisé la même approche pour créer une méthode « pick » pour prendre le membre sélectionné et insérer la mention dans l'éditeur. Voici le code avec lequel je me suis retrouvé :

mention-manager.ts


export class MentionsManager {
  private mentions: IMention[] = [];

  constructor(
    private source: MentionsAPI,
    private ui: IMentionsUI
  ) {}

  async fetch(take: number, query?: string): Promise<IMention[]> {
    try {
      const result = await this.source(take, query);
      if (result.length === 0) return [];
      this.mentions = result;

      return result;
    } catch {
      return [];
    }
  }

  pick(id: string, location: Range): void {
    const mention = this.mentions.find((m) => m.userId === id);
    if (!mention) return;

    this.ui.insertMention(mention, location);
  }
}


Copier après la connexion
❓ Uncertainty
The Range interface is a built-in type that is really difficult to mock and this interface leaks an implementation detail into my business logic. I feel like there might've been a better way to do this.

Rétrospection

Dans l’ensemble, je pense que je me suis retrouvé avec un code simple et facile à modifier. Il y a encore des parties de ce code que je n'aime pas vraiment. Je voulais que la logique métier pilote l'interface utilisateur, mais le code ressemblait davantage à un simple magasin qui effectue également un seul appel à l'interface utilisateur. Je me demande si je pourrais envelopper plus fortement l'interface utilisateur pour mieux utiliser le gestionnaire.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!