"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Aprendendo TDD fazendo: Marcando membros no Rich Text Editor da Umbraco

Aprendendo TDD fazendo: Marcando membros no Rich Text Editor da Umbraco

Publicado em 2024-11-02
Navegar:789

Learning TDD by doing: Tagging members in Umbraco

No sistema que estou construindo, preciso poder mencionar os membros do Umbraco em texto no site. Para fazer isso, preciso construir uma extensão para o Rich Text Editor da Umbraco: TinyMCE.

Contexto

Como editor de conteúdo, quero marcar membros em uma mensagem ou artigo para que eles sejam notificados sobre novos conteúdos sobre eles.

Eu observei implementações semelhantes, como no Slack ou no X. O Slack usa uma tag html especial para menções durante a escrita, mas depois envia os dados para o backend com um token com um formato específico. Decidi adotar uma abordagem semelhante, mas por enquanto esqueça a etapa de tradução. No conteúdo, uma menção ficará assim:


@D_Inventor

Exploração inicial

Antes de começar a construir, eu estava procurando maneiras de entrar no TinyMCE em Umbraco. Esta é uma das coisas que menos gosto de estender no backoffice da Umbraco. Já fiz isso antes e achei mais fácil estender o editor se eu criasse um decorador no tinyMceService da Umbraco em AngularJS. Na documentação do TinyMCE, encontrei um recurso chamado 'autoCompleters', que fazia exatamente o que eu precisava, então aí estava meu gancho no editor. Meu código inicial (sem nenhum teste ainda), ficou assim:


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
      ): Promise
        // 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;
}


Estou usando vite e typescript neste projeto, mas não tenho nenhum tipo para TinyMCE instalado. Por enquanto vou manter any e tentar evitar o TinyMCE tanto quanto possível.

Construindo com TDD

Decidi usar a brincadeira para testar. Achei um começo fácil e rapidamente consegui fazer algo funcionar.

✅ Sucesso
Aprendi uma nova ferramenta para testes unitários em código frontend. Apliquei com sucesso a ferramenta para escrever um frontend com testes unitários

Eu escrevi meu primeiro teste:

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);
  });
});


Fiquei um tanto surpreso com o rigor do compilador datilografado. Trabalhar em etapas aqui realmente significava não adicionar nada que você ainda não esteja usando. Por exemplo, eu queria adicionar uma referência à "UI", porque sabia que iria usá-la mais tarde, mas não consegui compilar o MentionsManager até usar tudo o que coloquei no construtor.

Depois de algumas rodadas de vermelho, verde e refatoração, acabei com estes testes:

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);
  });
});


Com essa lógica implementada, eu poderia buscar menções de qualquer fonte e mostrá-las no RTE por meio do gancho 'fetch'.
Usei a mesma abordagem para criar um método 'pick' para pegar o membro selecionado e inserir a menção no editor. Este é o código que acabei com:

mention-manager.ts


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

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

  async fetch(take: number, query?: string): Promise {
    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);
  }
}


❓ Incerteza
A interface Range é um tipo integrado que é realmente difícil de simular e essa interface vaza detalhes de implementação em minha lógica de negócios. Sinto que poderia haver uma maneira melhor de fazer isso.

Retrospecto

No geral, acho que acabei com um código simples e fácil de alterar. Ainda há partes deste código que eu realmente não gosto. Eu queria que a lógica de negócios conduzisse a UI, mas o código acabou mais como um armazenamento simples que também faz uma única chamada para a UI. Eu me pergunto se eu poderia envolver a interface do usuário com mais força para aproveitar melhor o gerenciador.

Declaração de lançamento Este artigo está reproduzido em: https://dev.to/d_inventor/learning-tdd-by-doing-tagging-members-in-umbracos-rich-text-editor-29o4?1 Se houver alguma violação, entre em contato com study_golang @163.com excluir
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3