Commit bcb20014 authored by Bradley Hilton's avatar Bradley Hilton Committed by GitHub

Add more tests, pre-commit hooks, and codecov support

parent 6724c14b
......@@ -70,7 +70,7 @@ jobs:
- run:
name: Test TypeScript Code
command: npm run unit-tests
command: npm run test-and-coverage
- persist_to_workspace:
root: ~/repo
......
codecov:
branch: develop
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: 40...100
status:
project:
enabled: yes
threshold: 0.5%
patch: no
changes: no
comment:
layout: "header, diff, changes"
behavior: default
......@@ -6162,6 +6162,12 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
"os-shim": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
"integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=",
"dev": true
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
......@@ -6334,6 +6340,17 @@
}
}
},
"pre-commit": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz",
"integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=",
"dev": true,
"requires": {
"cross-spawn": "5.1.0",
"spawn-sync": "1.0.15",
"which": "1.2.14"
}
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
......@@ -6825,6 +6842,16 @@
"integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=",
"dev": true
},
"spawn-sync": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
"integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=",
"dev": true,
"requires": {
"concat-stream": "1.6.2",
"os-shim": "0.1.3"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
......
......@@ -10,9 +10,9 @@
"compile": "gulp",
"go-publish": "gulp publish",
"unit-tests": "ts-node ./tests/runner.ts",
"check-coverage": "nyc --reporter=lcov --reporter=html npm run unit-tests && nyc report",
"view-coverage": "npm run check-coverage && http-server coverage -p 9082 -c-1",
"submit-codecov": "codecov"
"test-and-coverage": "nyc npm run unit-tests && nyc report",
"view-coverage": "npm run test-and-coverage && http-server coverage -p 9082 -c-1",
"submit-codecov": "codecov --file=coverage/coverage-final.json"
},
"repository": {
"type": "git",
......@@ -49,6 +49,7 @@
"http-server": "^0.11.1",
"nedb": "^1.8.0",
"nyc": "^11.7.1",
"pre-commit": "^1.2.2",
"tap-bark": "^1.0.0",
"ts-node": "^6.0.2",
"tslint": "^5.9.1"
......@@ -71,9 +72,15 @@
".ts"
],
"reporter": [
"lcov",
"json",
"html"
],
"all": true
}
},
"pre-commit": [
"lint",
"compile",
"unit-tests"
]
}
......@@ -34,8 +34,6 @@ export class AppManager {
private isLoaded: boolean;
constructor(rlStorage: AppStorage, logStorage: AppLogStorage, rlBridges: AppBridges) {
console.log('Constructed the AppManager.');
if (rlStorage instanceof AppStorage) {
this.storage = rlStorage;
} else {
......@@ -56,8 +54,8 @@ export class AppManager {
this.apps = new Map<string, ProxiedApp>();
this.parser = new AppPackageParser(this);
this.compiler = new AppCompiler(this);
this.parser = new AppPackageParser();
this.compiler = new AppCompiler();
this.accessorManager = new AppAccessorManager(this);
this.listenerManager = new AppListenerManger(this);
this.commandManager = new AppSlashCommandManager(this);
......@@ -129,7 +127,7 @@ export class AppManager {
const aff = new AppFabricationFulfillment();
try {
const result = await this.getParser().parseZip(item.zip);
const result = await this.getParser().parseZip(this.getCompiler(), item.zip);
aff.setAppInfo(result.info);
aff.setImplementedInterfaces(result.implemented.getValues());
......@@ -141,7 +139,7 @@ export class AppManager {
item.compiled = result.compiledFiles;
const app = this.getCompiler().toSandBox(item);
const app = this.getCompiler().toSandBox(this, item);
this.apps.set(item.id, app);
aff.setApp(app);
} catch (e) {
......@@ -307,7 +305,7 @@ export class AppManager {
public async add(zipContentsBase64d: string, enable = true): Promise<AppFabricationFulfillment> {
const aff = new AppFabricationFulfillment();
const result = await this.getParser().parseZip(zipContentsBase64d);
const result = await this.getParser().parseZip(this.getCompiler(), zipContentsBase64d);
aff.setAppInfo(result.info);
aff.setImplementedInterfaces(result.implemented.getValues());
......@@ -334,7 +332,7 @@ export class AppManager {
// Now that is has all been compiled, let's get the
// the App instance from the source.
const app = this.getCompiler().toSandBox(created);
const app = this.getCompiler().toSandBox(this, created);
this.apps.set(app.getID(), app);
aff.setApp(app);
......@@ -382,7 +380,7 @@ export class AppManager {
public async update(zipContentsBase64d: string): Promise<AppFabricationFulfillment> {
const aff = new AppFabricationFulfillment();
const result = await this.getParser().parseZip(zipContentsBase64d);
const result = await this.getParser().parseZip(this.getCompiler(), zipContentsBase64d);
aff.setAppInfo(result.info);
aff.setImplementedInterfaces(result.implemented.getValues());
......@@ -421,7 +419,7 @@ export class AppManager {
// Now that is has all been compiled, let's get the
// the App instance from the source.
const app = this.getCompiler().toSandBox(stored);
const app = this.getCompiler().toSandBox(this, stored);
// Store it temporarily so we can access it else where
this.apps.set(app.getID(), app);
......@@ -499,7 +497,7 @@ export class AppManager {
throw new Error(`No App found by the id of: "${ appId }"`);
}
this.apps.set(item.id, this.getCompiler().toSandBox(item));
this.apps.set(item.id, this.getCompiler().toSandBox(this, item));
const rl = this.apps.get(item.id);
await this.initializeApp(item, rl, false);
......
......@@ -83,7 +83,7 @@ export class MessageBuilder implements IMessageBuilder {
}
if (!this.msg.attachments[position]) {
throw new Error(`No attachment found at the index of "${ position }" to replace.`);
throw new Error(`No attachment found at the index of "${ position }" to remove.`);
}
this.msg.attachments.splice(position, 1);
......@@ -98,7 +98,7 @@ export class MessageBuilder implements IMessageBuilder {
public getMessage(): IMessage {
if (!this.msg.room) {
throw new Error('The Room Value is required.');
throw new Error('The "room" property is required.');
}
return this.msg;
......
......@@ -16,6 +16,10 @@ export class MessageExtender implements IMessageExtender {
}
public addCustomField(key: string, value: any): IMessageExtender {
if (!this.msg.customFields) {
this.msg.customFields = {};
}
if (this.msg.customFields[key]) {
throw new Error(`The message already contains a custom field by the key: ${ key }`);
}
......
......@@ -69,6 +69,10 @@ export class RoomBuilder implements IRoomBuilder {
}
public addCustomField(key: string, value: object): IRoomBuilder {
if (typeof this.room.customFields !== 'object') {
this.room.customFields = {};
}
this.room.customFields[key] = value;
return this;
}
......
......@@ -20,7 +20,7 @@ export class AppCompiler {
private readonly compilerOptions: ts.CompilerOptions;
private libraryFiles: { [s: string]: ICompilerFile };
constructor(private readonly manager: AppManager) {
constructor() {
this.compilerOptions = {
target: ts.ScriptTarget.ES2017,
module: ts.ModuleKind.CommonJS,
......@@ -257,7 +257,7 @@ export class AppCompiler {
return result;
}
public toSandBox(storage: IAppStorageItem): ProxiedApp {
public toSandBox(manager: AppManager, storage: IAppStorageItem): ProxiedApp {
const files = this.storageFilesToCompiler(storage.compiled);
if (typeof files[path.normalize(storage.info.classFile)] === 'undefined') {
......@@ -313,18 +313,20 @@ export class AppCompiler {
throw new MustContainFunctionError(storage.info.classFile, 'getRequiredApiVersion');
}
const app = new ProxiedApp(this.manager, storage, rl as App, customRequire);
const app = new ProxiedApp(manager, storage, rl as App, customRequire);
this.manager.getLogStorage().storeEntries(app.getID(), logger);
manager.getLogStorage().storeEntries(app.getID(), logger);
return app;
}
private isValidFile(file: ICompilerFile): boolean {
return file.name
&& file.name.trim() !== ''
if (!file || !file.name || !file.content) {
return false;
}
return file.name.trim() !== ''
&& path.normalize(file.name)
&& file.content
&& file.content.trim() !== '';
}
......
import { Utilities } from '../misc/Utilities';
export enum AppInterface {
// Messages
IPreMessageSentPrevent = 'IPreMessageSentPrevent',
......@@ -26,6 +28,6 @@ export class AppImplements {
}
public getValues(): { [int: string]: boolean } {
return Object.assign({}, this.implemented);
return Utilities.deepCloneAndFreeze(this.implemented);
}
}
import { AppManager } from '../AppManager';
import { RequiredApiVersionError } from '../errors';
import { AppCompiler } from './AppCompiler';
import { ICompilerFile } from './ICompilerFile';
import { IParseZipResult } from './IParseZipResult';
......@@ -16,11 +16,11 @@ export class AppPackageParser {
private allowedIconExts: Array<string> = ['.png', '.jpg', '.jpeg', '.gif'];
private appsTsDefVer: string;
constructor(private readonly manager: AppManager) {
constructor() {
this.appsTsDefVer = this.getTsDefVersion();
}
public async parseZip(zipBase64: string): Promise<IParseZipResult> {
public async parseZip(compiler: AppCompiler, zipBase64: string): Promise<IParseZipResult> {
const zip = new AdmZip(new Buffer(zipBase64, 'base64'));
const infoZip = zip.getEntry('app.json');
let info: IAppInfo;
......@@ -72,7 +72,7 @@ export class AppPackageParser {
const languageContent = this.getLanguageContent(zip);
// Compile all the typescript files to javascript
const result = this.manager.getCompiler().toJs(info, tsFiles);
const result = compiler.toJs(info, tsFiles);
tsFiles = result.files;
const compiledFiles: { [s: string]: string } = {};
......
// tslint:disable:max-line-length
import { Expect, Test } from 'alsatian';
import { SimpleClass, TestsData } from '../test-data/utilities';
import { Expect, SetupFixture, Test } from 'alsatian';
import { SimpleClass, TestInfastructureSetup } from '../test-data/utilities';
import { AppManager } from '../../src/server/AppManager';
import { AppBridges } from '../../src/server/bridges';
import { AppCompiler, AppPackageParser } from '../../src/server/compiler';
import { AppAccessorManager, AppListenerManger, AppSettingsManager, AppSlashCommandManager } from '../../src/server/managers';
export class AppManagerTestFixture {
private testingInfastructure: TestInfastructureSetup;
@SetupFixture
public setupFixture() {
this.testingInfastructure = new TestInfastructureSetup();
}
@Test('Setup of the AppManager')
public setupAppManager() {
const manager = new AppManager(TestsData.getAppStorage(), TestsData.getLogStorage(), TestsData.getAppBridges());
const manager = new AppManager(this.testingInfastructure.getAppStorage(), this.testingInfastructure.getLogStorage(), this.testingInfastructure.getAppBridges());
Expect(manager.getStorage()).toBe(TestsData.getAppStorage());
Expect(manager.getLogStorage()).toBe(TestsData.getLogStorage());
Expect(manager.getBridges()).toBe(TestsData.getAppBridges());
Expect(manager.getStorage()).toBe(this.testingInfastructure.getAppStorage());
Expect(manager.getLogStorage()).toBe(this.testingInfastructure.getLogStorage());
Expect(manager.getBridges()).toBe(this.testingInfastructure.getAppBridges());
Expect(manager.areAppsLoaded()).toBe(false);
}
@Test('Invalid Storage and Bridge')
public invalidInstancesPassed() {
const invalid = new SimpleClass();
Expect(() => new AppManager(invalid as any, invalid as any, invalid as any)).toThrowError(Error, 'Invalid instance of the AppStorage.');
Expect(() => new AppManager(TestsData.getAppStorage(), invalid as any, invalid as any)).toThrowError(Error, 'Invalid instance of the AppLogStorage.');
Expect(() => new AppManager(TestsData.getAppStorage(), TestsData.getLogStorage(), invalid as any)).toThrowError(Error, 'Invalid instance of the AppBridges');
Expect(() => new AppManager(this.testingInfastructure.getAppStorage(), invalid as any, invalid as any)).toThrowError(Error, 'Invalid instance of the AppLogStorage.');
Expect(() => new AppManager(this.testingInfastructure.getAppStorage(), this.testingInfastructure.getLogStorage(), invalid as any)).toThrowError(Error, 'Invalid instance of the AppBridges');
}
@Test('Ensure Managers are Valid Types')
public verifyManagers() {
const manager = new AppManager(this.testingInfastructure.getAppStorage(), this.testingInfastructure.getLogStorage(), this.testingInfastructure.getAppBridges());
Expect(manager.getParser() instanceof AppPackageParser).toBe(true);
Expect(manager.getCompiler() instanceof AppCompiler).toBe(true);
Expect(manager.getAccessorManager() instanceof AppAccessorManager).toBe(true);
Expect(manager.getBridges() instanceof AppBridges).toBe(true);
Expect(manager.getListenerManager() instanceof AppListenerManger).toBe(true);
Expect(manager.getCommandManager() instanceof AppSlashCommandManager).toBe(true);
Expect(manager.getSettingsManager() instanceof AppSettingsManager).toBe(true);
}
}
import { IHttpPreRequestHandler, IHttpPreResponseHandler } from '@rocket.chat/apps-ts-definition/accessors';
import { Expect, Test } from 'alsatian';
import { HttpExtend } from '../../../src/server/accessors';
export class HttpExtendAccessorTestFixture {
@Test()
public basicHttpExtend() {
Expect(() => new HttpExtend()).not.toThrow();
const he = new HttpExtend();
Expect(he.getDefaultHeaders()).toEqual(new Map());
Expect(he.getDefaultParams()).toEqual(new Map());
Expect(he.getPreRequestHandlers()).toBeEmpty();
Expect(he.getPreResponseHandlers()).toBeEmpty();
}
@Test()
public defaultHeadersInHttpExtend() {
const he = new HttpExtend();
Expect(() => he.provideDefaultHeader('Auth', 'token')).not.toThrow();
Expect(he.getDefaultHeaders().size).toBe(1);
Expect(he.getDefaultHeaders().get('Auth')).toBe('token');
Expect(() => he.provideDefaultHeaders({
Auth: 'token2',
Another: 'thing',
})).not.toThrow();
Expect(he.getDefaultHeaders().size).toBe(2);
Expect(he.getDefaultHeaders().get('Auth')).toBe('token2');
Expect(he.getDefaultHeaders().get('Another')).toBe('thing');
}
@Test()
public defaultParamsInHttpExtend() {
const he = new HttpExtend();
Expect(() => he.provideDefaultParam('id', 'abcdefg')).not.toThrow();
Expect(he.getDefaultParams().size).toBe(1);
Expect(he.getDefaultParams().get('id')).toBe('abcdefg');
Expect(() => he.provideDefaultParams({
id: 'zyxwvu',
count: '4',
})).not.toThrow();
Expect(he.getDefaultParams().size).toBe(2);
Expect(he.getDefaultParams().get('id')).toBe('zyxwvu');
Expect(he.getDefaultParams().get('count')).toBe('4');
}
@Test()
public preRequestHandlersInHttpExtend() {
const he = new HttpExtend();
const preRequestHandler: IHttpPreRequestHandler = {
executePreHttpRequest: function _thing(url, req) {
return new Promise((resolve) => resolve(req));
},
};
Expect(() => he.providePreRequestHandler(preRequestHandler)).not.toThrow();
Expect(he.getPreRequestHandlers()).not.toBeEmpty();
}
@Test()
public preResponseHandlersInHttpExtend() {
const he = new HttpExtend();
const preResponseHandler: IHttpPreResponseHandler = {
executePreHttpResponse: function _thing(res) {
return new Promise((resolve) => resolve(res));
},
};
Expect(() => he.providePreResponseHandler(preResponseHandler)).not.toThrow();
Expect(he.getPreResponseHandlers()).not.toBeEmpty();
}
}
import { IMessage } from '@rocket.chat/apps-ts-definition/messages';
import { Expect, Test } from 'alsatian';
import { TestData } from '../../test-data/utilities';
import { MessageBuilder } from '../../../src/server/accessors';
export class MessageBuilderAccessorTestFixture {
@Test()
public basicMessageBuilder() {
Expect(() => new MessageBuilder()).not.toThrow();
Expect(() => new MessageBuilder(TestData.getMessage())).not.toThrow();
}
@Test()
public settingOnMessageBuilder() {
const mbOnce = new MessageBuilder();
// setData just replaces the passed in object, so let's treat it differently
Expect(mbOnce.setData({text: 'hello' } as IMessage)).toBe(mbOnce);
Expect((mbOnce as any).msg.text).toBe('hello');
const msg: IMessage = {} as IMessage;
const mb = new MessageBuilder(msg);
Expect(mb.setRoom(TestData.getRoom())).toBe(mb);
Expect(msg.room).toEqual(TestData.getRoom());
Expect(mb.setSender(TestData.getUser())).toBe(mb);
Expect(msg.sender).toEqual(TestData.getUser());
Expect(mb.setText('testing, yo!')).toBe(mb);
Expect(msg.text).toEqual('testing, yo!');
Expect(mb.setEmojiAvatar(':ghost:')).toBe(mb);
Expect(msg.emoji).toEqual(':ghost:');
Expect(mb.setAvatarUrl('https://rocket.chat/')).toBe(mb);
Expect(msg.avatarUrl).toEqual('https://rocket.chat/');
Expect(mb.setUsernameAlias('Some Bot')).toBe(mb);
Expect(msg.alias).toEqual('Some Bot');
Expect(msg.attachments).not.toBeDefined();
Expect(mb.addAttachment({ color: '#0ff' })).toBe(mb);
Expect(msg.attachments).toBeDefined();
Expect(msg.attachments).not.toBeEmpty();
Expect(msg.attachments[0].color).toEqual('#0ff');
Expect(mb.setAttachments([])).toBe(mb);
Expect(msg.attachments).toBeEmpty();
delete msg.attachments;
Expect(() => mb.replaceAttachment(1, {})).toThrowError(Error, 'No attachment found at the index of "1" to replace.');
Expect(mb.addAttachment({})).toBe(mb);
Expect(mb.replaceAttachment(0, { color: '#f0f'})).toBe(mb);
Expect(msg.attachments[0].color).toEqual('#f0f');
Expect(mb.removeAttachment(0)).toBe(mb);
Expect(msg.attachments).toBeEmpty();
delete msg.attachments;
Expect(() => mb.removeAttachment(4)).toThrowError(Error, 'No attachment found at the index of "4" to remove.');
Expect(mb.setEditor(TestData.getUser('msg-editor-id'))).toBe(mb);
Expect(msg.editor).toBeDefined();
Expect(msg.editor.id).toEqual('msg-editor-id');
Expect(mb.getMessage()).toBe(msg);
delete msg.room;
Expect(() => mb.getMessage()).toThrowError(Error, 'The "room" property is required.');
}
}
import { IMessage } from '@rocket.chat/apps-ts-definition/messages';
import { Expect, Test } from 'alsatian';
import { TestData } from '../../test-data/utilities';
import { MessageExtender } from '../../../src/server/accessors';
export class MessageExtenderAccessorTestFixture {
@Test()
public basicMessageExtender() {
Expect(() => new MessageExtender({} as IMessage)).not.toThrow();
Expect(() => new MessageExtender(TestData.getMessage())).not.toThrow();
}
@Test()
public usingMessageExtender() {
const msg: IMessage = {} as IMessage;
const me = new MessageExtender(msg);
Expect(msg.attachments).toBeDefined();
Expect(msg.attachments).toBeEmpty();
Expect(me.addCustomField('thing', 'value')).toBe(me);
Expect(msg.customFields).toBeDefined();
Expect(msg.customFields.thing).toBe('value');
Expect(() => me.addCustomField('thing', 'second')).toThrowError(Error, 'The message already contains a custom field by the key: thing');
Expect(me.addAttachment({})).toBe(me);
Expect(msg.attachments.length).toBe(1);
Expect(me.addAttachments([{ collapsed: true }, { color: '#f00' }])).toBe(me);
Expect(msg.attachments.length).toBe(3);
Expect(me.getMessage()).not.toBe(msg);
Expect(me.getMessage()).toEqual(msg);
}
}
import { IMessage } from '@rocket.chat/apps-ts-definition/messages';
import { AsyncTest, Expect, SetupFixture } from 'alsatian';
import { MessageRead } from '../../../src/server/accessors';
import { IMessageBridge } from '../../../src/server/bridges';
import { TestData } from '../../test-data/utilities';
export class MessageReadAccessorTestFixture {
private msg: IMessage;
private mockMsgBridgeWithMsg: IMessageBridge;
private mockMsgBridgeNoMsg: IMessageBridge;
@SetupFixture
public setupFixture() {
this.msg = TestData.getMessage();
const theMsg = this.msg;
this.mockMsgBridgeWithMsg = {
getById(id, appId): Promise<IMessage> {
return Promise.resolve(theMsg);
},
} as IMessageBridge;
this.mockMsgBridgeNoMsg = {
getById(id, appId): Promise<IMessage> {
return Promise.resolve(undefined);
},
} as IMessageBridge;
}
@AsyncTest()
public async expectDataFromMessageRead() {
Expect(() => new MessageRead(this.mockMsgBridgeWithMsg, 'testing-app')).not.toThrow();
const mr = new MessageRead(this.mockMsgBridgeWithMsg, 'testing-app');
Expect(await mr.getById('fake')).toBeDefined();
Expect(await mr.getById('fake')).toEqual(this.msg);
Expect(await mr.getSenderUser('fake')).toBeDefined();
Expect(await mr.getSenderUser('fake')).toEqual(this.msg.sender);
Expect(await mr.getRoom('fake')).toBeDefined();
Expect(await mr.getRoom('fake')).toEqual(this.msg.room);
}
@AsyncTest()
public async doNotExpectDataFromMessageRead() {
Expect(() => new MessageRead(this.mockMsgBridgeNoMsg, 'testing')).not.toThrow();
const nomr = new MessageRead(this.mockMsgBridgeNoMsg, 'testing');
Expect(await nomr.getById('fake')).not.toBeDefined();
Expect(await nomr.getSenderUser('fake')).not.toBeDefined();
Expect(await nomr.getRoom('fake')).not.toBeDefined();
}
}
import { Expect, Test } from 'alsatian';
import { Modify } from '../../../src/server/accessors';
import { AppBridges } from '../../../src/server/bridges';
export class ModifyAccessorTestFixture {
@Test()
public useModify() {
Expect(() => new Modify({} as AppBridges, 'testing')).not.toThrow();
const md = new Modify({} as AppBridges, 'testing');
Expect(md.getCreator()).toBeDefined();
Expect(md.getExtender()).toBeDefined();
Expect(md.getNotifer()).toBeDefined();
Expect(md.getUpdater()).toBeDefined();
}
}
import { IRoom, RoomType } from '@rocket.chat/apps-ts-definition/rooms';
import { Expect, Test } from 'alsatian';
import { TestData } from '../../test-data/utilities';
import { RoomBuilder } from '../../../src/server/accessors';
export class RoomBuilderAccessorTestFixture {
@Test()
public basicRoomBuilder() {
Expect(() => new RoomBuilder()).not.toThrow();
Expect(() => new RoomBuilder(TestData.getRoom())).not.toThrow();
}
@Test()
public settingOnRoomBuilder() {
const rbOnce = new RoomBuilder();
// setData just replaces the passed in object, so let's treat it differently
Expect(rbOnce.setData({ displayName: 'Testing Channel' } as IRoom)).toBe(rbOnce);
Expect((rbOnce as any).room.displayName).toBe('Testing Channel');
const room: IRoom = {} as IRoom;
const rb = new RoomBuilder(room);
Expect(rb.setDisplayName('Just a Test')).toBe(rb);
Expect(room.displayName).toEqual('Just a Test');
Expect(rb.setSlugifiedName('just_a_test')).toBe(rb);
Expect(room.slugifiedName).toEqual('just_a_test');
Expect(rb.setType(RoomType.CHANNEL)).toBe(rb);
Expect(room.type).toEqual(RoomType.CHANNEL);
Expect(rb.setCreator(TestData.getUser())).toBe(rb);
Expect(room.creator).toEqual(TestData.getUser());
Expect(rb.addUsername('testing.username')).toBe(rb);
Expect(room.usernames).not.toBeEmpty();
Expect(room.usernames[0]).toEqual('testing.username');
Expect(rb.addUsername('another.username')).toBe(rb);
Expect(room.usernames.length).toBe(2);
Expect(rb.setUsernames([])).toBe(rb);
Expect(room.usernames).toBeEmpty();
Expect(rb.setDefault(true)).toBe(rb);<