Ответы персонала в ветке комментариев никуда не уведомляли: ученик видел ответ, только если сам возвращался в урок. У тредов вопросов письмо есть, у комментариев не было — из-за этого ответ под уроком 6.3 «Холст» висел непрочитанным, пока его не продублировали руками. - sendCommentReplyEmail: тот же шаблон ДС-2, что у ответов в тредах, с текстом ответа внутри письма и кнопкой на урок. Свой лимит превью 4000 символов: ввод разрешает 10 000, а порог вопросов (2000) резал бы развёрнутые разборы. - commentReplyRecipient: правила «шлём / не шлём» вынесены чистой функцией и покрыты тестами — не шлём при выключенной настройке, не-staff, удалённом родителе, ответе самому себе и пустой почте. - notifyStudentOnCommentReply: тумблер в админке рядом с уведомлением о фидбеке, по умолчанию включён. - Удалён неподключённый дубль comment-actions.ts: компонент комментариев ходит в lib/actions/student-actions.ts, а в дубле жил свой лимит 2000 — правка в нём не давала эффекта. Проверено: type-check и lint чистые (3 ошибки линтера — существующий долг в quick-enroll-modal и kinescope-player), vitest 13/13, письмо отправлено боем и доставлено (Resend, last_event=delivered). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { commentReplyRecipient } from "../comment-notify";
|
|
|
|
const parent = {
|
|
userId: "student-1",
|
|
deleted: false,
|
|
user: { email: "student@example.com", name: "Владимир" },
|
|
};
|
|
|
|
describe("commentReplyRecipient", () => {
|
|
it("отдаёт автора родительского комментария, когда админ ответил ученику", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "admin-1",
|
|
replierRole: "admin",
|
|
parent,
|
|
})
|
|
).toEqual({ email: "student@example.com", name: "Владимир" });
|
|
});
|
|
|
|
it("работает и для куратора", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "curator-1",
|
|
replierRole: "curator",
|
|
parent,
|
|
})
|
|
).toEqual({ email: "student@example.com", name: "Владимир" });
|
|
});
|
|
|
|
it("молчит при выключенной настройке", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: false,
|
|
replierId: "admin-1",
|
|
replierRole: "admin",
|
|
parent,
|
|
})
|
|
).toBeNull();
|
|
});
|
|
|
|
it("молчит, когда отвечает не staff", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "student-2",
|
|
replierRole: "student",
|
|
parent,
|
|
})
|
|
).toBeNull();
|
|
});
|
|
|
|
it("молчит без родительского комментария — это корневая запись, а не ответ", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "admin-1",
|
|
replierRole: "admin",
|
|
parent: null,
|
|
})
|
|
).toBeNull();
|
|
});
|
|
|
|
it("молчит, если родительский комментарий удалён", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "admin-1",
|
|
replierRole: "admin",
|
|
parent: { ...parent, deleted: true },
|
|
})
|
|
).toBeNull();
|
|
});
|
|
|
|
it("не пишет письмо самому себе", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "student-1",
|
|
replierRole: "admin",
|
|
parent,
|
|
})
|
|
).toBeNull();
|
|
});
|
|
|
|
it("молчит, когда у автора нет почты", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "admin-1",
|
|
replierRole: "admin",
|
|
parent: { ...parent, user: { email: " ", name: "Без почты" } },
|
|
})
|
|
).toBeNull();
|
|
});
|
|
|
|
it("отдаёт пустое имя, если в карточке его нет", () => {
|
|
expect(
|
|
commentReplyRecipient({
|
|
notifyEnabled: true,
|
|
replierId: "admin-1",
|
|
replierRole: "admin",
|
|
parent: { ...parent, user: { email: "student@example.com", name: "" } },
|
|
})
|
|
).toEqual({ email: "student@example.com", name: "" });
|
|
});
|
|
});
|