Our team has implemented major features that improve course management for instructors and facilitate the Q&A between instructors and students. They are
- Instructor group
- Anonymous Post
- Group visibility enhancements
- Instructor-endorsed Answers
The Instructor Group feature introduces a dedicated Instructors group with moderation-level capabilities needed for course operations. This includes:
- Topic and post moderation permissions across categories (configured on default category privileges)
- Global ability to ban users
- Global ability to endorse posts
This supports teaching staff workflows without requiring full administrator access.
- Open Admin Control Panel and go to Manage Users
- Select the target user
- Use Manage Groups and add the user to Instructors
After joining the group, the user receives instructor moderation privileges and can perform endorsement/ban actions permitted by policy.
- Add a test user to the
Instructorsgroup - Check global privileges for that group
- Expected:
groups:banandgroups:posts:endorseare enabled
- Inspect default category (
cid = -1) privileges forInstructors - Expected: moderation privileges such as
groups:topics:deleteandgroups:posts:view_deletedare enabled
Run the test with:
npm test test/categories.jsInstructorsgroup exists or is created- Installer-equivalent privileges are granted
- Global privilege checks return enabled values for ban and endorsement
- Default category privilege checks return enabled moderation values
- Installer parity: The test mirrors the privileges assigned by setup logic (
categories.giveatcid = -1and global grants), so it validates the intended deployment behavior(works on default). - Permission coverage: It checks both global and category-level outcomes, covering the two privilege scopes the feature relies on.
- Regression protection: If group creation or privilege assignment changes through user's future actions, this test fails immediately at the permission boundary.
The Anonymous Post feature allows registered users to create posts and replies without revealing their identity. When posting anonymously:
- The post displays as authored by "Anonymous" with a generic avatar
- The real author's identity is preserved internally for moderation purposes
- User statistics (post count) are still attributed to the real author
- Navigate to any category and click New Topic
- In the composer, enable the Post Anonymously option (only available to logged-in users)
- Write your title and content as usual and submit
Your topic will display as authored by "Anonymous." Your actual username will not be visible to other users, but your post count will still increment.
- Open an existing topic and click Reply
- Enable the Post Anonymously option in the composer
- Write your reply and submit
Your reply will appear as authored by "Anonymous" with full standard functionality (voting, editing, etc.).
- Log in and navigate to any category
- Click New Topic, enable the anonymous option, and submit a post
- Expected: Topic displays with author "Anonymous" and a generic avatar; your profile post count increments
- Log in and open any existing topic
- Click Reply, enable the anonymous option, and submit
- Expected: Reply displays as "Anonymous"; threading and timestamp are correct
- Log in and create a topic or reply without enabling the anonymous option
- Expected: Post displays with your real username and avatar as normal
- Log out and navigate to any topic
- Expected: The anonymous posting option is not available to guests
- Editing an anonymous post should leave it displayed as "Anonymous"
- Moderators can view the real author via the preserved
realUidfield - Anonymous posts appear in search results with "Anonymous" as the author
test/posts/anonymous.js
test/posts.js
Run the tests with:
npm test test/posts/anonymous.jsAND
npm test test/posts.jsThe automated tests cover both helper-level behavior and integration behavior for anonymous posting.
Unit/Helper Tests (test/posts/anonymous.js)
getAnonymousUserData(): Returns a correct, consistently structured anonymous user object; each call produces a new instance
isAnonymousPost(): Returns true only when isAnonymous === 1 (strict integer check); handles null, undefined, and other falsy/truthy values gracefully
applyAnonymousFields(): Correctly sets isAnonymous, realUid, and uid on a post object; preserves other existing properties
overrideUserDisplay(): Overrides display data for anonymous posts only; leaves non-anonymous posts untouched; preserves realUid for moderation
getStatsUid(): Returns realUid for anonymous posts and uid for normal posts, ensuring statistics are attributed to the correct user
getRealAuthorUid(): Returns realUid when present, falls back to a fallbackUid otherwise; handles edge cases like realUid = 0
Integration Tests (test/posts.js, describe('anonymous posting', ...))
Anonymous post creation metadata: Confirms anonymous topics/replies are stored with isAnonymous, display uid = 0, internal realUid, and anonymousAliasId
Stable identity mapping: Confirms same user in same thread keeps one alias ID and different users in same thread receive different alias IDs
Thread-level display behavior: Confirms topic payloads expose generated anonymous alias display names while preserving generic anonymous profile fields
Summary/single-post display behavior: Confirms apiPosts.get and apiPosts.getSummary display plain Anonymous and do not expose realUid
- Complete function coverage: Every exported function has dedicated tests covering both the happy path and edge cases
- Branch coverage: Every conditional is tested in both its true and false states, including type coercion edge cases (e.g., string
'1'vs integer1) - Data integrity: Tests verify that
realUidis preserved throughout the post lifecycle, which is critical for moderation and ActivityPub federation - Backward compatibility: Tests confirm that non-anonymous posts are unaffected by the feature
- Scope: The module consists entirely of pure data-transformation functions with no direct database calls, so unit tests at this level provide complete coverage of the module's logic. Database integration and UI behavior are covered by NodeBB's existing broader test suite.
The Anonymous Post feature allows registered users to create posts and replies without revealing their identity. When posting anonymously:
- The post displays as authored by "Anonymous" with a generic avatar
- The real author's identity is preserved internally for moderation purposes
- User statistics (post count) are still attributed to the real author
- Navigate to any category and click New Topic
- In the composer, enable the Post Anonymously option (only available to logged-in users)
- Write your title and content as usual and submit
Your topic will display as authored by "Anonymous." Your actual username will not be visible to other users, but your post count will still increment.
- Open an existing topic and click Reply
- Enable the Post Anonymously option in the composer
- Write your reply and submit
Your reply will appear as authored by "Anonymous" with full standard functionality (voting, editing, etc.).
- Log in and navigate to any category
- Click New Topic, enable the anonymous option, and submit a post
- Expected: Topic displays with author "Anonymous" and a generic avatar; your profile post count increments
- Log in and open any existing topic
- Click Reply, enable the anonymous option, and submit
- Expected: Reply displays as "Anonymous"; threading and timestamp are correct
- Log in and create a topic or reply without enabling the anonymous option
- Expected: Post displays with your real username and avatar as normal
- Log out and navigate to any topic
- Expected: The anonymous posting option is not available to guests
- Editing an anonymous post should leave it displayed as "Anonymous"
- Moderators can view the real author via the preserved
realUidfield - Anonymous posts appear in search results with "Anonymous" as the author
test/posts/anonymous.js
test/posts.js
Run the tests with:
npm test test/posts/anonymous.jsAND
npm test test/posts.jsThe automated tests cover both helper-level behavior and integration behavior for anonymous posting.
Unit/Helper Tests (test/posts/anonymous.js)
getAnonymousUserData(): Returns a correct, consistently structured anonymous user object; each call produces a new instance
isAnonymousPost(): Returns true only when isAnonymous === 1 (strict integer check); handles null, undefined, and other falsy/truthy values gracefully
applyAnonymousFields(): Correctly sets isAnonymous, realUid, and uid on a post object; preserves other existing properties
overrideUserDisplay(): Overrides display data for anonymous posts only; leaves non-anonymous posts untouched; preserves realUid for moderation
getStatsUid(): Returns realUid for anonymous posts and uid for normal posts, ensuring statistics are attributed to the correct user
getRealAuthorUid(): Returns realUid when present, falls back to a fallbackUid otherwise; handles edge cases like realUid = 0
Integration Tests (test/posts.js, describe('anonymous posting', ...))
Anonymous post creation metadata: Confirms anonymous topics/replies are stored with isAnonymous, display uid = 0, internal realUid, and anonymousAliasId
Stable identity mapping: Confirms same user in same thread keeps one alias ID and different users in same thread receive different alias IDs
Thread-level display behavior: Confirms topic payloads expose generated anonymous alias display names while preserving generic anonymous profile fields
Summary/single-post display behavior: Confirms apiPosts.get and apiPosts.getSummary display plain Anonymous and do not expose realUid
- Complete function coverage: Every exported function has dedicated tests covering both the happy path and edge cases
- Branch coverage: Every conditional is tested in both its true and false states, including type coercion edge cases (e.g., string
'1'vs integer1) - Data integrity: Tests verify that
realUidis preserved throughout the post lifecycle, which is critical for moderation and ActivityPub federation - Backward compatibility: Tests confirm that non-anonymous posts are unaffected by the feature
- Scope: The module consists entirely of pure data-transformation functions with no direct database calls, so unit tests at this level provide complete coverage of the module's logic. Database integration and UI behavior are covered by NodeBB's existing broader test suite.
Note that ACP stands for Admin Control Panel
Two UI/data enhancements were added to improve role visibility:
- Admin user list groups column: Admin Control's Manage Users now includes group badges per user.
- Topic list group title label: Topic list payload includes author
groupTitle, allowing templates to show role labels beside usernames.
- Open ACP(Admin Control Panel) → Manage Users
- View each row in the users table
- Expected: Groups appear as badges in the groups column (excluding
registered-users)
- Ensure a user belongs to a group with a configured title
- Create a topic using that user
- Open category/recent topic list
- Expected: User role label (group title) appears with the author display
- Add a user to a non-system group (for example,
Teaching Assistants) - Open ACP → Manage Users
- Expected: The user row shows the added group badge in the groups column
- Open ACP → Manage Users for any normal account
- Expected:
registered-usersis not shown as a badge in the groups column
- Add a user to a group with a title
- Create a topic with that user
- Open topic list view
- Expected: The author data includes and displays the
groupTitle
test/controllers-admin.js
test/topics.js
Run the targeted tests with:
npm test test/controllers-admin.js
npm test test/topics.js- Admin users endpoint returns user groups for display and filters out
registered-users - Topic list payload includes
user.groupTitlefor authors with role titles
- Direct payload validation: Tests assert the exact API fields consumed by the UI for both ACP users table and topic lists.
- Positive and negative checks: They verify expected inclusion (custom groups and titles) and exclusion (
registered-users), reducing false confidence from one-sided assertions. - Integration-level confidence: Coverage spans controller/API output and topic retrieval flow, which are the main surfaces affected by the feature changes.
This feature allows instructors, or anyone with the endorse privilege, to endorse certain posts. Under the endorsed posts, a message will be displayed in green, to denote that the post is endorsed.
If you have the endorse privilege, in the post dropdown menu, you should see the "endorse post" button pop up. Click it to endorse a post. If the post is already endorsed, the button will become "unendorse post" so that you can cancel your endorsement. The post dropdown menu is located at the 3 dots at the bottom right corner of every post.
Users with the endorse privilege can repeat the steps above to ensure that this feature is working properly. After clicking the endorse button, if the green message "The post is endorsed by an instructor" appears under the post content, then the endorsement is successful. Clicking "unendorse post" would make the message disappear.
The automated tests for this feature are all located in test/posts.js. You can run them by running the command
npm run test test/posts.jsin the terminal.
The tests comprehensively test that every post is associated with the endorse field, and that the functions reading the fields, like loadPostTools() and functions modifying the field, like the API calls, are working correctly. Th tests also checks the privilege system to eusure that only users with the privilege can endorse, and that the privilege can be granted like all other global privileges.
The tests should be comprehensive because they check all the intended and unintended usages of the functions, and verify that they are all handled properly. Almost all lines involved in implementing this functionality has been checked by the tests. This high coverage ensures that all code has been tested.