mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-15 11:03:30 +01:00
fix: poll builders (#10783)
* fix: poll builders
- Fixed validations
- Added missing documentation
- Removed redundant code
- Consistency™️
* fix: tests
* feat: missing answers test
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { PollLayoutType } from 'discord-api-types/v10';
|
import { PollLayoutType, type RESTAPIPoll } from 'discord-api-types/v10';
|
||||||
import { describe, test, expect } from 'vitest';
|
import { describe, test, expect } from 'vitest';
|
||||||
import { PollAnswerMediaBuilder, PollBuilder, PollQuestionBuilder } from '../../src/index.js';
|
import { PollAnswerMediaBuilder, PollBuilder, PollQuestionBuilder } from '../../src/index.js';
|
||||||
|
|
||||||
@@ -7,22 +7,33 @@ const dummyData = {
|
|||||||
text: '.',
|
text: '.',
|
||||||
},
|
},
|
||||||
answers: [],
|
answers: [],
|
||||||
};
|
} satisfies RESTAPIPoll;
|
||||||
|
|
||||||
|
const dummyDataWithAnswer = {
|
||||||
|
...dummyData,
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
poll_media: {
|
||||||
|
text: '.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} satisfies RESTAPIPoll;
|
||||||
|
|
||||||
describe('Poll', () => {
|
describe('Poll', () => {
|
||||||
describe('Poll question', () => {
|
describe('Poll question', () => {
|
||||||
test('GIVEN a poll with pre-defined question text THEN return valid toJSON data', () => {
|
test('GIVEN a poll with pre-defined question text THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder({ question: { text: 'foo' } });
|
const poll = new PollBuilder({ ...dummyDataWithAnswer, question: { text: 'foo' } });
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ ...dummyData, question: { text: 'foo' } });
|
expect(poll.toJSON()).toStrictEqual({ ...dummyDataWithAnswer, question: { text: 'foo' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with question text THEN return valid toJSON data', () => {
|
test('GIVEN a poll with question text THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder();
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
poll.setQuestion({ text: 'foo' });
|
poll.setQuestion({ text: 'foo' });
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ ...dummyData, question: { text: 'foo' } });
|
expect(poll.toJSON()).toStrictEqual({ ...dummyDataWithAnswer, question: { text: 'foo' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with invalid question THEN throws error', () => {
|
test('GIVEN a poll with invalid question THEN throws error', () => {
|
||||||
@@ -32,21 +43,21 @@ describe('Poll', () => {
|
|||||||
|
|
||||||
describe('Poll duration', () => {
|
describe('Poll duration', () => {
|
||||||
test('GIVEN a poll with pre-defined duration THEN return valid toJSON data', () => {
|
test('GIVEN a poll with pre-defined duration THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder({ duration: 1, ...dummyData });
|
const poll = new PollBuilder({ duration: 1, ...dummyDataWithAnswer });
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ duration: 1, ...dummyData });
|
expect(poll.toJSON()).toStrictEqual({ duration: 1, ...dummyDataWithAnswer });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with duration THEN return valid toJSON data', () => {
|
test('GIVEN a poll with duration THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder(dummyData);
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
poll.setDuration(1);
|
poll.setDuration(1);
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ duration: 1, ...dummyData });
|
expect(poll.toJSON()).toStrictEqual({ duration: 1, ...dummyDataWithAnswer });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with invalid duration THEN throws error', () => {
|
test('GIVEN a poll with invalid duration THEN throws error', () => {
|
||||||
const poll = new PollBuilder(dummyData);
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
expect(() => poll.setDuration(999).toJSON()).toThrowError();
|
expect(() => poll.setDuration(999).toJSON()).toThrowError();
|
||||||
});
|
});
|
||||||
@@ -54,21 +65,21 @@ describe('Poll', () => {
|
|||||||
|
|
||||||
describe('Poll layout type', () => {
|
describe('Poll layout type', () => {
|
||||||
test('GIVEN a poll with pre-defined layout type THEN return valid toJSON data', () => {
|
test('GIVEN a poll with pre-defined layout type THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder({ layout_type: PollLayoutType.Default, ...dummyData });
|
const poll = new PollBuilder({ ...dummyDataWithAnswer, layout_type: PollLayoutType.Default });
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ layout_type: PollLayoutType.Default, ...dummyData });
|
expect(poll.toJSON()).toStrictEqual({ layout_type: PollLayoutType.Default, ...dummyDataWithAnswer });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with layout type THEN return valid toJSON data', () => {
|
test('GIVEN a poll with layout type THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder(dummyData);
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
poll.setLayoutType(PollLayoutType.Default);
|
poll.setLayoutType(PollLayoutType.Default);
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ layout_type: PollLayoutType.Default, ...dummyData });
|
expect(poll.toJSON()).toStrictEqual({ layout_type: PollLayoutType.Default, ...dummyDataWithAnswer });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with invalid layout type THEN throws error', () => {
|
test('GIVEN a poll with invalid layout type THEN throws error', () => {
|
||||||
const poll = new PollBuilder(dummyData);
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
// @ts-expect-error Invalid layout type
|
// @ts-expect-error Invalid layout type
|
||||||
expect(() => poll.setLayoutType(-1).toJSON()).toThrowError();
|
expect(() => poll.setLayoutType(-1).toJSON()).toThrowError();
|
||||||
@@ -77,21 +88,21 @@ describe('Poll', () => {
|
|||||||
|
|
||||||
describe('Poll multi select', () => {
|
describe('Poll multi select', () => {
|
||||||
test('GIVEN a poll with pre-defined multi select enabled THEN return valid toJSON data', () => {
|
test('GIVEN a poll with pre-defined multi select enabled THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder({ allow_multiselect: true, ...dummyData });
|
const poll = new PollBuilder({ allow_multiselect: true, ...dummyDataWithAnswer });
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ allow_multiselect: true, ...dummyData });
|
expect(poll.toJSON()).toStrictEqual({ allow_multiselect: true, ...dummyDataWithAnswer });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with multi select enabled THEN return valid toJSON data', () => {
|
test('GIVEN a poll with multi select enabled THEN return valid toJSON data', () => {
|
||||||
const poll = new PollBuilder(dummyData);
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
poll.setMultiSelect();
|
poll.setMultiSelect();
|
||||||
|
|
||||||
expect(poll.toJSON()).toStrictEqual({ allow_multiselect: true, ...dummyData });
|
expect(poll.toJSON()).toStrictEqual({ allow_multiselect: true, ...dummyDataWithAnswer });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with invalid multi select value THEN throws error', () => {
|
test('GIVEN a poll with invalid multi select value THEN throws error', () => {
|
||||||
const poll = new PollBuilder(dummyData);
|
const poll = new PollBuilder(dummyDataWithAnswer);
|
||||||
|
|
||||||
// @ts-expect-error Invalid multi-select value
|
// @ts-expect-error Invalid multi-select value
|
||||||
expect(() => poll.setMultiSelect('string').toJSON()).toThrowError();
|
expect(() => poll.setMultiSelect('string').toJSON()).toThrowError();
|
||||||
@@ -99,6 +110,12 @@ describe('Poll', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Poll answers', () => {
|
describe('Poll answers', () => {
|
||||||
|
test('GIVEN a poll without answers THEN throws error', () => {
|
||||||
|
const poll = new PollBuilder(dummyData);
|
||||||
|
|
||||||
|
expect(() => poll.toJSON()).toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
test('GIVEN a poll with pre-defined answer THEN returns valid toJSON data', () => {
|
test('GIVEN a poll with pre-defined answer THEN returns valid toJSON data', () => {
|
||||||
const poll = new PollBuilder({
|
const poll = new PollBuilder({
|
||||||
...dummyData,
|
...dummyData,
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ export const pollQuestionPredicate = z.object({ text: z.string().min(1).max(300)
|
|||||||
|
|
||||||
export const pollAnswerMediaPredicate = z.object({
|
export const pollAnswerMediaPredicate = z.object({
|
||||||
text: z.string().min(1).max(55),
|
text: z.string().min(1).max(55),
|
||||||
emoji: emojiPredicate.nullish(),
|
emoji: emojiPredicate.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pollAnswerPredicate = z.object({ poll_media: pollAnswerMediaPredicate });
|
export const pollAnswerPredicate = z.object({ poll_media: pollAnswerMediaPredicate });
|
||||||
|
|
||||||
export const pollPredicate = z.object({
|
export const pollPredicate = z.object({
|
||||||
question: pollQuestionPredicate,
|
question: pollQuestionPredicate,
|
||||||
answers: z.array(pollAnswerPredicate).max(10),
|
answers: z.array(pollAnswerPredicate).min(1).max(10),
|
||||||
duration: z.number().min(1).max(768).optional(),
|
duration: z.number().min(1).max(768).optional(),
|
||||||
allow_multiselect: z.boolean().optional(),
|
allow_multiselect: z.boolean().optional(),
|
||||||
layout_type: z.nativeEnum(PollLayoutType).optional(),
|
layout_type: z.nativeEnum(PollLayoutType).optional(),
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export class PollBuilder implements JSONEncodable<RESTAPIPoll> {
|
|||||||
* @param updater - The function to update the question with
|
* @param updater - The function to update the question with
|
||||||
*/
|
*/
|
||||||
public updateQuestion(updater: (builder: PollQuestionBuilder) => void): this {
|
public updateQuestion(updater: (builder: PollQuestionBuilder) => void): this {
|
||||||
updater((this.data.question ??= new PollQuestionBuilder()));
|
updater(this.data.question);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,16 @@ export interface PollAnswerData extends Omit<APIPollAnswer, 'answer_id' | 'poll_
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PollAnswerBuilder {
|
export class PollAnswerBuilder {
|
||||||
|
/**
|
||||||
|
* The API data associated with this poll answer.
|
||||||
|
*/
|
||||||
protected readonly data: PollAnswerData;
|
protected readonly data: PollAnswerData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new poll answer from API data.
|
||||||
|
*
|
||||||
|
* @param data - The API data to create this poll answer with
|
||||||
|
*/
|
||||||
public constructor(data: Partial<Omit<APIPollAnswer, 'answer_id'>> = {}) {
|
public constructor(data: Partial<Omit<APIPollAnswer, 'answer_id'>> = {}) {
|
||||||
this.data = {
|
this.data = {
|
||||||
...structuredClone(data),
|
...structuredClone(data),
|
||||||
@@ -35,8 +43,9 @@ export class PollAnswerBuilder {
|
|||||||
*
|
*
|
||||||
* @param updater - The function to update the media with
|
* @param updater - The function to update the media with
|
||||||
*/
|
*/
|
||||||
public updateMedia(updater: (builder: PollAnswerMediaBuilder) => void) {
|
public updateMedia(updater: (builder: PollAnswerMediaBuilder) => void): this {
|
||||||
updater((this.data.poll_media ??= new PollAnswerMediaBuilder()));
|
updater(this.data.poll_media);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,10 +56,12 @@ export class PollAnswerBuilder {
|
|||||||
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
||||||
*/
|
*/
|
||||||
public toJSON(validationOverride?: boolean): Omit<APIPollAnswer, 'answer_id'> {
|
public toJSON(validationOverride?: boolean): Omit<APIPollAnswer, 'answer_id'> {
|
||||||
|
const { poll_media, ...rest } = this.data;
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
...structuredClone(this.data),
|
...structuredClone(rest),
|
||||||
// Disable validation because the pollAnswerPredicate below will validate this as well
|
// Disable validation because the pollAnswerPredicate below will validate this as well
|
||||||
poll_media: this.data.poll_media?.toJSON(false),
|
poll_media: poll_media.toJSON(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
validate(pollAnswerPredicate, data, validationOverride);
|
validate(pollAnswerPredicate, data, validationOverride);
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { pollAnswerMediaPredicate } from './Assertions.js';
|
|||||||
import { PollMediaBuilder } from './PollMedia.js';
|
import { PollMediaBuilder } from './PollMedia.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder that creates API-compatible JSON data for poll answers.
|
* A builder that creates API-compatible JSON data for the media of a poll answer.
|
||||||
*/
|
*/
|
||||||
export class PollAnswerMediaBuilder extends PollMediaBuilder {
|
export class PollAnswerMediaBuilder extends PollMediaBuilder {
|
||||||
/**
|
/**
|
||||||
* Sets the emoji for this poll answer.
|
* Sets the emoji for this poll answer media.
|
||||||
*
|
*
|
||||||
* @param emoji - The emoji to use
|
* @param emoji - The emoji to use
|
||||||
*/
|
*/
|
||||||
@@ -18,18 +18,21 @@ export class PollAnswerMediaBuilder extends PollMediaBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the emoji for this poll answer.
|
* Clears the emoji for this poll answer media.
|
||||||
*/
|
*/
|
||||||
public clearEmoji(): this {
|
public clearEmoji(): this {
|
||||||
this.data.emoji = undefined;
|
this.data.emoji = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc PollMediaBuilder.toJSON}
|
||||||
|
*/
|
||||||
public override toJSON(validationOverride?: boolean): APIPollMedia {
|
public override toJSON(validationOverride?: boolean): APIPollMedia {
|
||||||
const clone = structuredClone(this.data);
|
const clone = structuredClone(this.data);
|
||||||
|
|
||||||
validate(pollAnswerMediaPredicate, clone, validationOverride);
|
validate(pollAnswerMediaPredicate, clone, validationOverride);
|
||||||
|
|
||||||
return clone as APIPollMedia;
|
return clone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import type { APIPollMedia } from 'discord-api-types/v10';
|
import type { APIPollMedia } from 'discord-api-types/v10';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base poll media builder that contains common symbols for poll media builders.
|
||||||
|
*/
|
||||||
export abstract class PollMediaBuilder {
|
export abstract class PollMediaBuilder {
|
||||||
|
/**
|
||||||
|
* The API data associated with this poll media.
|
||||||
|
*/
|
||||||
protected readonly data: Partial<APIPollMedia>;
|
protected readonly data: Partial<APIPollMedia>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import { PollMediaBuilder } from './PollMedia.js';
|
|||||||
* A builder that creates API-compatible JSON data for a poll question.
|
* A builder that creates API-compatible JSON data for a poll question.
|
||||||
*/
|
*/
|
||||||
export class PollQuestionBuilder extends PollMediaBuilder {
|
export class PollQuestionBuilder extends PollMediaBuilder {
|
||||||
|
/**
|
||||||
|
* {@inheritDoc PollMediaBuilder.toJSON}
|
||||||
|
*/
|
||||||
public override toJSON(validationOverride?: boolean): Omit<APIPollMedia, 'emoji'> {
|
public override toJSON(validationOverride?: boolean): Omit<APIPollMedia, 'emoji'> {
|
||||||
const clone = structuredClone(this.data);
|
const clone = structuredClone(this.data);
|
||||||
|
|
||||||
validate(pollQuestionPredicate, clone, validationOverride);
|
validate(pollQuestionPredicate, clone, validationOverride);
|
||||||
|
|
||||||
return clone as Omit<APIPollMedia, 'emoji'>;
|
return clone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user