4.2.2 changelog
Published
Entries marked Breaking change a working bot; the migration guide says what to do about them. Every release of the line is in the changelog.
Patch Changes
#658
e67cd51Thanks @l7aromeo! - A button, select menu or message modal whose guard returnsfalseis now acknowledged invisibly, with or without@Defer, so the user no longer sees "This interaction failed". The call still reportsran: false, and observers still see the outcome'denied'. A command, and a modal opened by a command, are left as they were: their only acknowledgement is a reply of their own.A test that expects
getResponse(interaction).callsto be[]after a denied click now sees['deferUpdate'], as a test of a handler with@Deferalready did.A guard that answers the click itself before returning
falseshould await that answer. A reply still on its way when the guard returns races the acknowledgement, and Discord refuses whichever comes second, possibly the reply.An app with both a route and a collector for one customId, where the guard returns
falseso the collector answers the click, should leave the click to one of them. Otherwise the two acknowledgements race, and the collector's can fail with "already acknowledged".#653
fcd7cbfThanks @l7aromeo! -warnUnanswerednow names an@Autocompletehandler that returns without callinginteraction.respond(), as it names any other handler that leaves its interaction unanswered. The user sees the autocomplete options fail to load in that case. The warning is shown once per handler: undermeocord start --dev, and in tests whose app setswarnUnanswered: true.@MeoCord({ warnUnanswered: false })turns it off. Nothing about the answer itself changes.#656
b951297Thanks @l7aromeo! -bindTheme's documentation now says that a bound function runs in the whole theme of the call that bound it, the server's and user's themes included. Every later call, such as another member's click on a collector, is answered in the starting member's theme. Its example comment said "the handler's theme", which left that out. Nothing about the behaviour changes.#652
f8369b6Thanks @l7aromeo! - A catalog group whose keys includeother, such asreasons: { spam: 'Spam', other: 'Other' }, is read as a group, as the types andexpectCompleteCatalogread it. Reading the group's own key,t('reasons'), shows the key, and in development logs the warning, as any other group does, where it showed theothertext. Only untyped code, such as code reading a JSON catalog, can make that call; to show the text, readt('reasons.other'). A plural is an object whose every key is a plural category, and a translation's plural without anotherform still falls back to the next catalog.#659
e9d25a4Thanks @l7aromeo! - The built-inhelpreply over 2,000 characters is now split into messages Discord shows as written:- A line longer than a message is cut after the last whole character that fits. An emoji, a flag, a joined emoji or a letter with its accent is no longer split across two messages.
- A message that would be only whitespace isn't sent. Discord refused it as empty, and the rest of the reply was lost with it.
A help text with no long line and no blank stretch at a cut is split as before.
#665
1a224e8Thanks @l7aromeo! - UnderuseStrictMocks(), a mock interaction is markedreplied,deferredorrespondedonce its answer resolves, as discord.js marks it after Discord takes the answer, rather than when the answer is called. Code that doesn't await an answer now fails in a test as it would on a bot:- an
editReply(),followUp()ordeleteReply()made before the first answer resolves rejects withInteractionNotReplied; - a second answer sent while the first is in flight passes discord.js's check and is refused with Discord's 40060, unless the first one failed;
replied,deferred,respondedandephemeralread in the same run as an un-awaited answer are as before it.
This catches overlaps within one synchronous run of the test's code. A mock answer resolves on the next microtask, so an answer still in flight across an
await, as a guard'svoid interaction.reply()followed byreturn falseis on a bot, isn't caught.In both modes, an answer that fails no longer undoes an answer made after it. A test could make the first of two un-awaited replies reject, and the interaction then read as unanswered though the second reply went through; it now reads as answered by the second. A failed answer's change is undone once every change after it is undone too, so an interaction whose answers all fail still reads as unanswered.
In default mode the flags are still set at the call. The first time an interaction does what a bot would refuse, an edit or a second answer while its first is in flight, a warning says what the bot would do. In the next major version (5.0), every mock interaction marks its answers when they resolve, as strict mocks do now.
- an
#663
3894f77Thanks @l7aromeo! - UnderuseStrictMocks(), a mock interaction'sephemeralisnulluntilreply()ordeferReply()decides it, as discord.js has it;update()anddeferUpdate()leave itnull. In default mode it still readsfalsebefore an answer, and a test reading it there logs a warning once. MeoCord's own reads while it answers log nothing. The next major version (5.0) readsnullwithout the call.#651
8a56464Thanks @l7aromeo! - A mock guild'semojis,stickers,scheduledEvents,autoModerationRulesandinvitesare managers, in both modes, as on a message's guild. Before, callingcreate(),fetch()orcache.get()on one threw aTypeError.create()resolves to a mock emoji, sticker, scheduled event or AutoMod rule in the guild, and caches it, as discord.js does.invites.create(channel)resolves to an invite for that channel in the guild, keyed by itscode, and caches nothing, as discord.js does.fetch(id)resolves to the cached item, or makes and caches one; a list fetch resolves to an empty collection.- Each item is named, and what discord.js formats from it computes: an emoji's mention (
String(emoji)) andidentifier, and a scheduled event's and an invite'surl.
#657
8d1d3fcThanks @l7aromeo! -MockedFunction's andMockInstance's documentation says what their types check: calling a mock is checked against the function it mocks, and the values its mock API takes and records, such asmockResolvedValue's and each ofmock.calls' arguments, are not, for any function. A partial fake such asguild.members.fetch.mockResolvedValue({ id })type-checks. Types are unchanged.#654
1cc5a4fThanks @l7aromeo! - Wherever MeoCord takes a class and is given something else, it now refuses the value and names where it was given, instead of crashing with an emptyTypeErroror reporting "This is a bug in MeoCord". TypeScript already rejects these values, so the refusal matters for plain JavaScript,anyand casts.@MeoCord'spresenter,cooldownStore,controllersandservicesrefuse an object or a function that isn't a class where the app is declared, for exampleApp: @MeoCord({ presenter }) takes a class implementing ResponsePresenter, not an object.- A service listed by a string or symbol token that no provider provides is refused as the app is created, naming the token.
- Stage entries given to
@UseGuard,@UseInterceptor,@UseFilter,@UsePipeand@MeoCord({ guards, interceptors, filters })refuse an arrow function, a method, an async function or a generator function: "a function is not a class". Afunctiondeclaration still works as a class. @Catchwarns about an arrow function, as it already does for any other value that isn't a class, and treats it as matching no error. Before this fix, every call whose error reached that filter rejected with aTypeErrorfrominstanceof. A bound error class still matches.- A testing module refuses a controller or an observer that isn't a class, and reports the refusal together with the other startup errors. After
reportAllStartupErrors(),fromApp()on an app whose@MeoCordwas refused throws the reason, not "is not a @MeoCord app".
A bot that started before starts unchanged.