«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Изучение TDD на практике: пометка участников в редакторе форматированного текста Umbraco

Изучение TDD на практике: пометка участников в редакторе форматированного текста Umbraco

Опубликовано 2 ноября 2024 г.
Просматривать:627

Learning TDD by doing: Tagging members in Umbraco

В системе, которую я создаю, мне нужна возможность упоминать членов Umbraco в тексте на веб-сайте. Для этого мне нужно создать расширение для редактора форматированного текста Umbraco: TinyMCE.

Контекст

Как редактор контента, я хочу отмечать участников в сообщении или статье, чтобы они получали уведомления о новом контенте о них.

Я рассматривал подобные реализации, например, в Slack или на X. Slack использует специальный html-тег для упоминаний во время записи, но затем отправляет данные на бэкенд с помощью токена определенного формата. Я решил применить аналогичный подход, но пока забыл про этап перевода. По содержанию упоминание будет выглядеть так:


@D_Inventor

Начальная разведка

Прежде чем приступить к созданию, я искал способы подключиться к TinyMCE в Umbraco. Это одна из моих наименее любимых вещей в бэк-офисе Umbraco. Однако я делал это раньше, и мне показалось, что проще всего расширить редактор, если я создам декоратор для tinyMceService от Umbraco в AngularJS. В документации TinyMCE я нашел функцию под названием «autoCompleters», которая делала именно то, что мне нужно, так что я смог подключиться к редактору. Мой первоначальный код (ещё без какого-либо тестирования) выглядел так:


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


В этом проекте я использую vite и typescript, но у меня не установлены типы для TinyMCE. На данный момент я оставлю Any и просто постараюсь избегать TinyMCE, насколько это возможно.

Строительство с TDD

Я решил использовать jest для тестирования. Мне было легко начать, и мне быстро удалось заставить что-то работать.

✅ Успех
Я изучил новый инструмент для модульного тестирования кода внешнего интерфейса. Я успешно применил этот инструмент для написания интерфейса с модульными тестами

Я написал свой первый тест:

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


Меня несколько удивила строгость компилятора машинописного текста. Работать здесь поэтапно на самом деле означало не добавлять ничего, что вы еще не используете. Например, я хотел добавить ссылку на «UI», потому что знал, что буду использовать ее позже, но не мог скомпилировать MentionsManager, пока не использовал все, что поместил в конструктор.

После нескольких раундов красного, зеленого и рефакторинга я получил следующие тесты:

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


При наличии этой логики я мог получать упоминания из любого источника и показывать их в RTE с помощью перехватчика fetch.
Я использовал тот же подход для создания метода «выбора», который берет выбранный элемент и вставляет упоминание в редактор. Вот код, который у меня получился:

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


❓ Неопределенность
Интерфейс Range — это встроенный тип, который действительно сложно имитировать, и этот интерфейс пропускает детали реализации в мою бизнес-логику. Мне кажется, что можно было бы сделать это лучше.

Ретроспектива

В целом, я думаю, у меня получился простой код, который легко изменить. Есть еще части этого кода, которые мне не очень нравятся. Я хотел, чтобы бизнес-логика управляла пользовательским интерфейсом, но в итоге код оказался больше похож на простое хранилище, которое также выполняет один вызов пользовательского интерфейса. Интересно, могу ли я более сильно обернуть пользовательский интерфейс, чтобы получить больше пользы от менеджера.

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/d_inventor/learning-tdd-by-doing-tagged-members-in-umbracos-rich-text-editor-29o4?1 Если есть какие-либо нарушения, свяжитесь с Study_golang. @163.com удалить
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3