diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f3b271..917f7ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,12 +2,8 @@ on: push: branches: - master - tags: - # Push events to matching like v1.0.0, v20.15.10, etc. - # If the tag is like "version 1.0.0" or "var 1.0.0" then it will not be matched - - 'v[0-9]+.[0-9]+.[0-9]+' -name: Release +name: Publish Release jobs: # New job, this job is called "docs" and will run on the latest version of Ubuntu # This job will run the "actions/checkout@v2" action to check out the code @@ -51,7 +47,7 @@ jobs: with: node-version: 16 - name: Install dependencies - run: npm ci + run: npm ci & npm i jest -g - name: Run tests run: npm run test @@ -90,7 +86,7 @@ jobs: uses: marvinpinto/action-automatic-releases@v1.2.1 with: repo_token: ${{ secrets.ACCESS_TOKEN }} - automatic_release_tag: v${{ env.NODE_VERSION }} + automatic_release_tag: ${{ env.NODE_VERSION }} draft: false prerelease: false title: Release ${{ env.NODE_VERSION }} diff --git a/.github/workflows/test_unit.yml b/.github/workflows/test_unit.yml new file mode 100644 index 0000000..761dd81 --- /dev/null +++ b/.github/workflows/test_unit.yml @@ -0,0 +1,38 @@ +on: +# Check every time a pull request is made in every branch + pull_request: + branches: + - '*' + +name: Test Unit +jobs: + docs: + name: Generate documentation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install JSDoc + run: npm i jsdoc -g + - name: Generate documentation + run: npm run docs-gh + + release: + name: Test Unit + needs: docs + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install dependencies + run: npm ci & npm i jest -g + - name: Run tests + run: npm run test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4c75fdb..410201c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ src/test node_modules docs -out \ No newline at end of file +out + +coverage \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..95c9ad7 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,195 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\Usuario\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: "jest-environment-node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: [ + // All files in the test directory that end with .test.js + "**/test/**/*.test.js" + ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/package.json b/package.json index 702a876..727ccd9 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "guilded-bot", - "version": "1.0.2", + "version": "1.0.3", "description": "A library for interacting with the Guilded API for your Guilded Bot", "main": "src/index.js", "scripts": { - "dev": "nodemon --trace-warnings src/test/index.js", + "dev": "nodemon --trace-warnings test/index.test.js", "start": "node src/index.js", "docs": "jsdoc --readme ./README.md -c ./jsdocs.json ./src/index.js --verbose", "docs-gh": "jsdoc --readme ./README.md -c ./jsdocs-gh.json ./src/index.js --verbose --destination ./docs", - "test": "echo \"Error: no tests for now\" && exit 0" + "test": "jest --coverage --verbose" }, "keywords": [ "guilded", diff --git a/src/classes/Structures/User/Member.js b/src/classes/Structures/User/Member.js index 13b3d45..0f86e0e 100644 --- a/src/classes/Structures/User/Member.js +++ b/src/classes/Structures/User/Member.js @@ -1,4 +1,4 @@ -const { User } = require("./User"); +const { User } = require("./user"); class Member { /** diff --git a/src/classes/Structures/User/MemberBan.js b/src/classes/Structures/User/MemberBan.js index 8c50a1b..d3b5f76 100644 --- a/src/classes/Structures/User/MemberBan.js +++ b/src/classes/Structures/User/MemberBan.js @@ -1,4 +1,4 @@ -const { UserSummary } = require('./UserSummary'); +const { UserSummary } = require('./usersummary'); class MemberBan { /** diff --git a/src/classes/Structures/User/User.js b/src/classes/Structures/User/User.js index e8f5062..da684aa 100644 --- a/src/classes/Structures/User/User.js +++ b/src/classes/Structures/User/User.js @@ -1,4 +1,4 @@ -const guser = require('../../../Helper/Members.js') +const guser = require('../../../helper/members.js') class User { /** diff --git a/src/classes/client/Client.js b/src/classes/client/Client.js index 242ae70..94525fa 100644 --- a/src/classes/client/Client.js +++ b/src/classes/client/Client.js @@ -1,6 +1,12 @@ const { EventEmitter } = require('events') -const { ClientWebSocket } = require('./WebSocket') +const { ClientWebSocket } = require('./websocket') +/** + * The main class for the Guilded.js library + * @class Client + * @extends EventEmitter + * @param {String} token The token of the bot + */ class Client extends EventEmitter { /** * Creates a new bot instance. @@ -29,68 +35,233 @@ class Client extends EventEmitter { * }); */ login () { + /** + * Create a new WebSocket connection + * @param {Client} client + * @returns {ClientWebSocket} + * @constructor + * @private + * @ignore + */ this.ws = new ClientWebSocket(this) + /** + * Connects the bot to the Guilded API + * @returns {Promise} + * @example + * client.ws.connect(); + * @example + * client.ws.connect().then(() => { + * console.log('Bot is ready!'); + * }); + * @private + * @ignore + */ this.ws.connect() + /** + * Emitted when the bot is ready + * @event Client#ready + * @param {Client} client The client that emitted the event + * @example + * client.on('ready', () => { + * console.log('Bot is ready!'); + * }); + */ this.ws.on('open', (client) => { this.emit('ready', client) }) + /** + * Emitted when the bot receives a message + * @event Client#message + * @param {Message} message + * @example + * client.on('serverMessageCreate', (message) => { + * console.log(message.content); + * }); + */ this.ws.on('messageCreated', (message) => { this.emit('serverMessageCreate', message) }) + /** + * Emitted when the bot receives a updated message + * @event Client#messageUpdate + * @param {Message} message + * @example + * client.on('serverMessageUpdate', (message) => { + * console.log(message.content); + * }); + */ this.ws.on('messageUpdated', (message) => { this.emit('serverMessageUpdate', message) }) + /** + * Emitted when the bot receives a deleted message + * @event Client#messageDelete + * @param {Message} message + * @example + * client.on('serverMessageDelete', (message) => { + * console.log(message.content); + * }); + */ this.ws.on('messageDeleted', (message) => { this.emit('serverMessageDelete', message) }) + /** + * Emitted when the bot receives a member join + * @event Client#memberJoin + * @param {Member} member + * @example + * client.on('serverMemberJoin', (member) => { + * console.log(member.nickname); + * }); + */ this.ws.on('memberAdded', (member) => { this.emit('serverMemberJoin', member) }) + /** + * Emitted when the bot receives a member leave + * @event Client#memberLeave + * @param {Member} member + * @example + * client.on('serverMemberLeft', (member) => { + * console.log(member.nickname); + * }); + */ this.ws.on('memberRemoved', (member) => { this.emit('serverMemberLeft', member) }) + /** + * Emitted when the bot receives a member ban + * @event Client#memberBan + * @param {MemberBan} memberBan + * @example + * client.on('serverMemberBan', (member) => { + * console.log(member.nickname); + * }); + */ this.ws.on('memberBanned', (member) => { this.emit('serverMemberBan', member) }) + /** + * Emitted when the bot receives a member unban + * @event Client#memberUnban + * @param {MemberBan} memberBan + * @example + * client.on('serverMemberUnban', (member) => { + * console.log(member.nickname); + * }); + */ this.ws.on('memberUnbanned', (member) => { this.emit('serverMemberUnban', member) }) + /** + * Emitted when the bot receives a member role update + * @event Client#memberRoleUpdate + * @param {Member} member + * @example + * client.on('serverMemberUpdate', (member) => { + * console.log(member.nickname); + * }); + */ this.ws.on('memberRolesUpdated', (member) => { this.emit('serverMemberUpdate', member) }) + /** + * Emitted when the bot receives a webhook create + * @event Client#webhookCreate + * @param {Webhook} webhook + * @example + * client.on('serverWebhookCreate', (webhook) => { + * console.log(webhook.name); + * }); + */ this.ws.on('webhookCreated', (webhook) => { this.emit('serverWebhookCreate', webhook) }) + /** + * Emitted when the bot receives a webhook update + * @event Client#webhookUpdate + * @param {Webhook} webhook + * @example + * client.on('serverWebhookUpdate', (webhook) => { + * console.log(webhook.name); + * }); + */ this.ws.on('webhookUpdated', (webhook) => { this.emit('serverWebhookUpdate', webhook) }) + /** + * Emitted when the bot receives a reaction add + * @event Client#reactionAdd + * @param {Reaction} reaction + * @example + * client.on('messageReactionCreated', (reaction) => { + * console.log(reaction.emoteId); + * }); + */ this.ws.on('messageReactionCreated', (reaction) => { this.emit('messageReactionCreated', reaction) }) + /** + * Emitted when the bot receives a reaction remove + * @event Client#reactionRemove + * @param {Reaction} reaction + * @example + * client.on('messageReactionDeleted', (reaction) => { + * console.log(reaction.emoteId); + * }); + */ this.ws.on('ChannelMessageReactionDeleted', (reaction) => { this.emit('ChannelMessageReactionDeleted', reaction) }) + /** + * Emitted when the bot receives a closed event + * @event Client#closed + * @example + * client.on('closed', () => { + * console.log('Bot is closed!'); + * }); + */ this.ws.on('closed', () => { this.emit('disconnect') }) + /** + * Emitted when the bot receives a error event + * @event Client#error + * @param {Error} error + * @example + * client.on('error', (error) => { + * console.log(error); + * }); + */ this.ws.on('error', (error) => { this.emit('error', error) }) + + /** + * Delete the client + * @event Client#destroy + * @example + * client.destroy(); + */ + this.destroy = async () => { + this.emit('destroy') + await this.ws.close() + } } } diff --git a/src/classes/client/WebSocket.js b/src/classes/client/WebSocket.js index ef7a9e1..8ac69a8 100644 --- a/src/classes/client/WebSocket.js +++ b/src/classes/client/WebSocket.js @@ -1,12 +1,21 @@ const { EventEmitter } = require("events"); const { WebSocket } = require("ws"); -const { Message } = require("../Structures/Message"); -const { User } = require("../Structures/User/User"); -const { Member } = require("../Structures/User/Member"); -const { MemberBan } = require("../Structures/User/MemberBan"); -const { Webhook } = require("../Structures/Webhook"); -const { Reaction } = require("../Structures/Reaction"); +const { Message } = require("../structures/message"); +const { User } = require("../structures/user/user"); +const { Member } = require("../structures/user/member"); +const { MemberBan } = require("../structures/user/memberban"); +const { Webhook } = require("../structures/webhook"); +const { Reaction } = require("../structures/reaction"); +/** + * The ClientWebSocket class + * @class ClientWebSocket + * @extends EventEmitter + * @param {Client} client + * @returns {ClientWebSocket} + * @constructor + * @private + */ class ClientWebSocket extends EventEmitter { /** * Create a new WebSocket connection diff --git a/src/classes/structures/Message.js b/src/classes/structures/Message.js index f7871a2..04ebe89 100644 --- a/src/classes/structures/Message.js +++ b/src/classes/structures/Message.js @@ -1,7 +1,7 @@ -const msgs = require("../../Helper/Messages"); -const guser = require("../../Helper/Members"); -const { User } = require("./User/User"); -const { Reaction } = require("./Reaction"); +const msgs = require("../../helper/messages"); +const guser = require("../../helper/members"); +const { User } = require("./user/user"); +const { Reaction } = require("./reaction"); /** * Represents a message diff --git a/src/helper/Members.js b/src/helper/Members.js index e225dcf..96eb258 100644 --- a/src/helper/Members.js +++ b/src/helper/Members.js @@ -1,7 +1,7 @@ -const { endpoints } = require('./Endpoints') +const { endpoints } = require('./endpoints') const axios = require('axios') -const { User } = require('../Classes/Structures/User/User') -const { Member } = require('../Classes/Structures/User/Member') +const { User } = require('../classes/structures/user/user') +const { Member } = require('../classes/structures/user/member') module.exports = new (class { /** @@ -10,6 +10,7 @@ module.exports = new (class { * @param {string} serverId * @param {string} token * @returns {User} The user object + * @ignore */ async getUser (userId, serverId, token) { const userData = await axios.get( @@ -41,6 +42,7 @@ module.exports = new (class { * @param {String} serverId * @param {String} token * @returns {Member} The member object + * @ignore */ async getMember (userId, serverId, token) { const memberData = await axios.get( diff --git a/src/helper/Messages.js b/src/helper/Messages.js index c49356b..af90f54 100644 --- a/src/helper/Messages.js +++ b/src/helper/Messages.js @@ -1,4 +1,4 @@ -const { endpoints } = require("./Endpoints"); +const { endpoints } = require("./endpoints"); const axios = require("axios"); module.exports = { @@ -47,7 +47,7 @@ module.exports = { .then((res) => { //Make a new message object const messageData = res.data.message; - const Messages = require("../Classes/Structures/Message"); + const Messages = require("../classes/structures/message"); const Message = new Messages.Message(authToken, messageData); return Message; }) @@ -104,7 +104,7 @@ module.exports = { .then((res) => { //Make a new message object const messageData = res.data.message; - const Messages = require("../Classes/Structures/Message"); + const Messages = require("../classes/structures/message"); const Message = new Messages.Message(authToken, messageData); return Message; }) diff --git a/src/helper/endpoints.js b/src/helper/endpoints.js index d119dd0..72e1ac9 100644 --- a/src/helper/endpoints.js +++ b/src/helper/endpoints.js @@ -12,17 +12,14 @@ module.exports.endpoints = { /** * Get the url for edit or delete channel message */ - MESSSAGE: (channelId, messageId) => - `${baseUrl}/channels/${channelId}/messages/${messageId}`, + MESSSAGE: (channelId, messageId) => `${baseUrl}/channels/${channelId}/messages/${messageId}`, /** /** * Get the url for the reactions of a message */ - REACT_MESSAGE: (channelId, messageId, emoteId) => - `${baseUrl}/channels/${channelId}/content/${messageId}/emotes/${emoteId}`, + REACT_MESSAGE: (channelId, messageId, emoteId) => `${baseUrl}/channels/${channelId}/content/${messageId}/emotes/${emoteId}`, /** * Get the url for the server members */ - SERVER_MEMBERS: (serverId, memberId) => - `${baseUrl}/servers/${serverId}/members/${memberId}`, + SERVER_MEMBERS: (serverId, memberId) => `${baseUrl}/servers/${serverId}/members/${memberId}`, }; diff --git a/src/index.js b/src/index.js index 89162c9..d67965b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,7 @@ 'use strict'; -module.exports.Client = require('./Classes/Client/Client.js').Client; -module.exports.MessageEmbed = require('./Classes/Structures/MessageEmbed.js').MessageEmbed; -module.exports.Version = require('../package.json').version; \ No newline at end of file +module.exports.Client = require('./classes/client/client.js').Client; +module.exports.Message = require('./classes/structures/message.js').Message; +module.exports.MessageEmbed = require('./classes/structures/messageEmbed.js').MessageEmbed; +module.exports.Version = require('../package.json').version; +module.exports.ClientWebSocket = require('./classes/client/websocket.js').ClientWebSocket; \ No newline at end of file diff --git a/test/client.test.js b/test/client.test.js new file mode 100644 index 0000000..e374325 --- /dev/null +++ b/test/client.test.js @@ -0,0 +1,31 @@ +const Guilded = require('../src/index.js'); +let client = new Guilded.Client("some token"); + +let message = new Guilded.Message(client.token, { + "id": "H6G4Q", + "createdBy": "y7Fqn", + "content": "Hey, this is a cool message!", + "channelId": "H7vG4", + "serverId": "K8zv6", + "mentions":{ + "users":[ + { + "id":"y7Fqn", + } + ], + "roles":[ + { + "id":"H7vG4", + } + ] + }, + "createdAt": "2020-10-10T20:20:20.000Z" +}, client); + +test('Check if message is defined', () => { + expect(message).toBeDefined(); +}); + +test('Check if message is an instance of Message', () => { + expect(message).toBeInstanceOf(Guilded.Message); +}); \ No newline at end of file diff --git a/test/example.test.js b/test/example.test.js new file mode 100644 index 0000000..daf60aa --- /dev/null +++ b/test/example.test.js @@ -0,0 +1,10 @@ +const Guilded = require('../src/index.js'); +let client = new Guilded.Client("some token"); + +test('Check if client is defined', () => { + expect(client).toBeDefined(); +}); + +test('Check if client is an instance of Client', () => { + expect(client).toBeInstanceOf(Guilded.Client); +}); \ No newline at end of file