一键导入
async-test-and-doc-sync
// Maintain test and documentation alignment — PHPUnit tests, async testing patterns, PHPDoc contracts, guide pages, and documentation workflow. Use when adding tests, updating docs, or changing public behavior.
// Maintain test and documentation alignment — PHPUnit tests, async testing patterns, PHPDoc contracts, guide pages, and documentation workflow. Use when adding tests, updating docs, or changing public behavior.
Maintain Builder classes — outbound payload construction, validation, serialization, component handling, and fromPart symmetry. Use when changing Builders or outbound Discord API payloads.
Maintain gateway event handlers — payload hydration, cache mutation, event return shapes, and handler registration. Use when touching WebSockets/Events, Handlers.php, or Event.php.
Work with DiscordPHP's infrastructure utilities — CacheWrapper, CacheConfig, BigInt, Multipart, Endpoint::bind URL templates, Collection base class, and domain Exceptions. Use when changing cache behavior, REST endpoint routing, file uploads, big-integer ID math, or adding/modifying exceptions.
Maintain interaction flow — Interaction typing, resolved data caching, command routing, autocomplete, modal responses, and interaction builders. Use when touching Interactions or slash command handling.
Maintain the optional prefix-command layer — DiscordCommandClient, Command class, parsing, aliases, cooldowns, and help system. Use when touching message-based command handling.
Maintain Part domain models — fillable attributes, mutators, typed nested data, save/fetch behavior, permission checks, PHPDoc, and repository bindings. Use when adding or modifying any Discord Part class.
| name | async-test-and-doc-sync |
| description | Maintain test and documentation alignment — PHPUnit tests, async testing patterns, PHPDoc contracts, guide pages, and documentation workflow. Use when adding tests, updating docs, or changing public behavior. |
Use this skill when adding or changing public behavior, updating builder validation or part semantics, writing or reviewing tests, updating PHPDoc blocks or guide pages, or touching anything user-facing enough that tests or docs should move with it.
This is alignment skill. Load it when the question is not "does the code work?" but "do tests prove it and do docs describe it?"
Keep tests, PHPDoc, and long-form documentation synchronized with the public behavior of parts, repositories, builders, and helpers:
tests/functions.php — wait() helper and getMockDiscord() factorytests/DiscordTestCase.php — integration base class with real channel setuptests/bootstrap.php — autoload, .env loading, singleton wiringphpunit.xml — test suite configuration, coverage settingstests/Builders/ModalBuilderTest.phptests/FunctionsTest.phptests/CollectionsTest.phptests/Parts/Channel/ChannelTest.phptests/Parts/Embed/EmbedTest.phptests/Parts/Channel/Message/MessageTest.phpguide/ — long-form RST documentation for parts, events, buildersdocs/ — Gatsby site source for published documentationREADME.md — getting started, installation, basic usageCONTRIBUTING.md — contributor workflow and code style expectationsTests and docs in this repo are not afterthoughts. They form a contract surface:
@property and @property-read annotations on parts are how IDEs and users discover magic properties. If code adds a fillable field or getter mutator without updating the docblock, the property is effectively invisible to consumers.PHPUnit\Framework\TestCase prove that isolated logic works without needing Discord credentials or a running event loop. These are fast and reliable.DiscordTestCase prove that async flows work against real Discord infrastructure. These require environment variables and a live bot token.guide/ and Gatsby pages in docs/ describe recommended patterns. They should only change when public behavior or preferred usage actually changes — not for internal refactors.If a change touches public behavior but skips one of these surfaces, the contract is incomplete.
TestCaseUse plain PHPUnit\Framework\TestCase when the logic under test is isolated from Discord I/O:
contains(), studly(), escapeMarkdown(), poly_strlen())push(), map(), filter(), from())getMockDiscord()jsonSerialize() on buildersThese tests need no token, no loop, and no network. They run in milliseconds.
Example: tests/Builders/ModalBuilderTest.php uses $this->expectException(\LogicException::class) and str_repeat('a', 101) to verify title length validation.
DiscordTestCaseUse DiscordTestCase only when the test must interact with real Discord infrastructure — sending/editing/deleting messages, pinning, creating invites, fetching message history, verifying embed hydration, or testing repository fetch()/freshen() against live API.
These tests require DISCORD_TOKEN, TEST_CHANNEL, and TEST_CHANNEL_NAME in .env or environment. DiscordSingleton shares a single connected client across the suite. Tests markTestSkipped if credentials are missing.
getMockDiscord() factoryDefined in tests/functions.php. Creates a minimal Discord instance with empty token and NullLogger — suitable for constructing parts and testing attribute access without connecting to gateway.
Test files mirror the source structure. Builder tests live in tests/Builders/, part tests in tests/Parts/{Family}/, and utility tests in tests/ root. Infrastructure files (bootstrap.php, functions.php, DiscordSingleton.php, DiscordTestCase.php) live at the tests/ root.
tests/Builders/{BuilderName}Test.phptests/Parts/{Family}/{PartName}Test.phptests/ rootTestCase, integration tests extend DiscordTestCaseDiscovers all *Test.php under tests/ recursively. Coverage tracks src/. Bootstrap is tests/bootstrap.php.
wait() bridgewait() in tests/functions.php bridges Promise-based async code into synchronous PHPUnit assertions:
function wait(callable $callback, float $timeout = TIMEOUT, ?callable $timeoutFn = null)
It works by scheduling $callback on the ReactPHP loop via futureTick(), passing a $resolve callable that stops the loop and captures the result, adding a timeout timer (default 10s), running the loop synchronously, and re-throwing any captured exception after loop stops.
Promise chain with assertion then resolve:
return wait(function (Discord $discord, $resolve) {
$this->channel()->sendMessage('test content')
->then(fn (Message $m) => $this->assertEquals('test content', $m->content))
->then($resolve, $resolve);
});
Custom timeout with fallback:
return wait(function (Discord $discord, $resolve) {
// ... async work ...
}, 10, fn () => $this->markTestIncomplete('Hit rate limit.'));
Key rules:
$resolve as both fulfillment and rejection handler at the end of the chain$callback receives (Discord $discord, callable $resolve) — use $discord for client accesswait() call from the test method so PHPUnit tracks itTIMEOUT constant)Every part class should have class-level docblock annotations for @property (read/write magic properties from $fillable), @property-read (computed properties from mutators or repos), @method (delegated magic methods), @since (version introduced), and @link (Discord API docs URL).
PHPDoc in this repo serves three purposes: IDE autocompletion (consumers rely on @property since all access goes through __get()), generated reference (docs tooling reads annotations), and static analysis (Mago uses docblocks for type checking).
$fillable → add @property with type@property-read with return type$repositories → add @property-read for the repository typestring to ?string) → update annotation/**
* @property string $id
* @property string $name
* @property-read Carbon $created_at
* @property-read MemberRepository $members
* @link https://discord.com/developers/docs/resources/guild
*/
class Guild extends Part
guide/ — long-form RST contentUser-facing guides organized by topic: basics.rst (getting started, intents), parts/ (per-resource docs), events/ (gateway handling), repositories.rst, message_builder.rst, components.rst, interactions.rst, permissions.rst, collection.rst, faq.rst.
docs/ — Gatsby sitePublished documentation website source. Build with cd docs && yarn install && yarn build.
README.mdInstallation, requirements, basic bot example. Update when minimum PHP version, major dependencies, or getting-started flow changes.
Docs should change when:
Docs should not change when:
| Purpose | Command |
|---|---|
| Run PHPUnit suite | composer unit |
| Static analysis | composer run-script mago-lint |
| Code style fixer | composer run-script cs |
| Non-mutating style check | ./vendor/bin/pint --test --config ./pint.json ./src |
| Docs site build | cd docs && yarn install && yarn build |
Integration tests require .env or shell variables: DISCORD_TOKEN (bot token), TEST_CHANNEL (channel ID), TEST_CHANNEL_NAME (channel name). When absent, DiscordSingleton falls back to getMockDiscord() and integration tests skip.
Use --filter to target specific tests: ./vendor/bin/phpunit --filter ModalBuilderTest
jsonSerialize() output shape matches Discord API expectationsnew() factory wires arguments correctlyfromPart() round-trip preserves meaningful fieldsgetCreatableAttributes() / getUpdatableAttributes() include correct fieldssave() overridesStop if you see:
@property docblock entryDiscordTestCase when TestCase would sufficewait() used in a test that never performs async I/OgetMockDiscord() used where real client interaction is actually needed@property or @property-read in class docblockwait() pattern with proper $resolve chainingTestCase, not DiscordTestCasecomposer unit passescomposer run-script mago-lint reports no new issuesTests prove behavior, docblocks declare surface, guides teach usage — if any of these drift from the code, users and tooling lose trust in the library.