다음을 수행하여 TDD 배우기: Umbraco의 리치 텍스트 편집기에서 멤버 태그 지정

Barbara Streisand
풀어 주다: 2024-10-08 06:21:01
원래의
806명이 탐색했습니다.

Learning TDD by doing: Tagging members in Umbraco

제가 구축하고 있는 시스템에서는 웹사이트에서 Umbraco 회원을 텍스트로 언급하는 기능이 필요합니다. 그러기 위해서는 Umbraco의 리치 텍스트 편집기인 TinyMCE에 대한 확장 기능을 구축해야 합니다.

문맥

콘텐츠 편집자로서 메시지나 기사에 회원을 태그하여 회원에 대한 새로운 콘텐츠에 대한 알림을 받고 싶습니다.

Slack이나 X에서와 같은 유사한 구현을 살펴보았습니다. Slack은 글을 쓰는 동안 언급을 위해 특별한 html 태그를 사용하지만, 그런 다음 특정 형식의 토큰과 함께 데이터를 백엔드로 보냅니다. 나는 비슷한 접근 방식을 취하기로 결정했지만 지금은 번역 단계를 잊어버렸습니다. 콘텐츠에서 언급은 다음과 같습니다.


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


로그인 후 복사

초기 탐사

빌딩을 시작하기 전에 Umbraco의 TinyMCE에 연결할 수 있는 방법을 찾고 있었습니다. 이것은 Umbraco 백오피스에서 제가 가장 좋아하는 확장 기능 중 하나입니다. 나는 이전에 이 작업을 수행한 적이 있으며 AngularJS에서 Umbraco의tinyMceService에 데코레이터를 생성하는 경우 편집기를 확장하는 것이 가장 쉽다는 것을 알았습니다. 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<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;
}


로그인 후 복사

이 프로젝트에서는 vite와 typescript를 사용하고 있는데 TinyMCE에 대한 유형이 설치되어 있지 않습니다. 지금은 any를 유지하고 TinyMCE를 최대한 피하려고 노력하겠습니다.

TDD로 빌드하기

테스트를 위해 jest를 사용하기로 결정했습니다. 나는 쉽게 시작할 수 있는 방법을 찾았고 신속하게 작업을 수행할 수 있었습니다.

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

첫 번째 테스트를 작성했습니다.

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에 표시할 수 있습니다.
동일한 접근 방식을 사용하여 선택한 멤버를 선택하고 해당 멘션을 편집기에 삽입하는 '선택' 메서드를 만들었습니다. 제가 만든 코드는 다음과 같습니다.

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


로그인 후 복사
❓ 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.

회고

전체적으로는 변경하기 쉬운 심플한 코드로 끝난 것 같습니다. 이 코드에는 여전히 마음에 들지 않는 부분이 있습니다. 나는 비즈니스 로직이 UI를 구동하기를 원했지만 코드는 결국 UI에 대한 단일 호출도 수행하는 단순한 저장소와 비슷해졌습니다. 관리자를 더 많이 활용할 수 있도록 UI를 더 강력하게 래핑할 수 있지 않을까 싶습니다.

위 내용은 다음을 수행하여 TDD 배우기: Umbraco의 리치 텍스트 편집기에서 멤버 태그 지정의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!