Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Early WIP for tests on the RoomListStore
Browse files Browse the repository at this point in the history
Part of element-hq/element-web#8922

This isn't complete as there's quite a lot to stub and much of it references static functions. The SettingsStore approach to stubbing here does work, but is far from ideal.
  • Loading branch information
turt2live committed Feb 26, 2019
1 parent 3d152da commit b7db917
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 0 deletions.
68 changes: 68 additions & 0 deletions src/settings/SettingsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ const LEVEL_ORDER = [
* be enabled).
*/
export default class SettingsStore {
// *********************
// FOR UNIT TESTS ONLY
// *********************
static _testFixture;

static set testFixture(val) {
if (val) {
console.warn("!! Using test fixture for settings !!");
console.warn("Values, watchers, etc are now the responsibility of the fixture.");
SettingsStore._testFixture = val;
} else {
console.warn("Test fixture for settings disabled.");
SettingsStore._testFixture = null;
}
}

// We support watching settings for changes, and do so only at the levels which are
// relevant to the setting. We pass the watcher on to the handlers and aggregate it
// before sending it off to the caller. We need to track which callback functions we
Expand Down Expand Up @@ -129,6 +145,10 @@ export default class SettingsStore {
* @returns {string} A reference to the watcher that was employed.
*/
static watchSetting(settingName, roomId, callbackFn) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.watchSetting(settingName, roomId, callbackFn);
}

const setting = SETTINGS[settingName];
const originalSettingName = settingName;
if (!setting) throw new Error(`${settingName} is not a setting`);
Expand Down Expand Up @@ -164,6 +184,10 @@ export default class SettingsStore {
* to cancel.
*/
static unwatchSetting(watcherReference) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.unwatchSetting(watcherReference);
}

if (!SettingsStore._watchers[watcherReference]) return;

for (const handlerName of Object.keys(SettingsStore._watchers[watcherReference])) {
Expand All @@ -184,6 +208,10 @@ export default class SettingsStore {
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms.
*/
static monitorSetting(settingName, roomId) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.monitorSetting(settingName, roomId);
}

if (!this._monitors[settingName]) this._monitors[settingName] = {};

const registerWatcher = () => {
Expand Down Expand Up @@ -223,6 +251,10 @@ export default class SettingsStore {
* @return {String} The display name for the setting, or null if not found.
*/
static getDisplayName(settingName, atLevel = "default") {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.getDisplayName(settingName, atLevel);
}

if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;

let displayName = SETTINGS[settingName].displayName;
Expand All @@ -239,6 +271,10 @@ export default class SettingsStore {
* @returns {string[]} The list of available feature names
*/
static getLabsFeatures() {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.getLabsFeatures();
}

const possibleFeatures = Object.keys(SETTINGS).filter((s) => SettingsStore.isFeature(s));

const enableLabs = SdkConfig.get()["enableLabs"];
Expand All @@ -253,6 +289,10 @@ export default class SettingsStore {
* @return {boolean} True if the setting is a feature.
*/
static isFeature(settingName) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.isFeature(settingName);
}

if (!SETTINGS[settingName]) return false;
return SETTINGS[settingName].isFeature;
}
Expand All @@ -265,6 +305,10 @@ export default class SettingsStore {
* @return {boolean} True if the feature is enabled, false otherwise
*/
static isFeatureEnabled(settingName, roomId = null) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.isFeatureEnabled(settingName, roomId);
}

if (!SettingsStore.isFeature(settingName)) {
throw new Error("Setting " + settingName + " is not a feature");
}
Expand All @@ -279,6 +323,10 @@ export default class SettingsStore {
* @returns {Promise} Resolves when the setting has been set.
*/
static setFeatureEnabled(settingName, value) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.setFeatureEnabled(settingName, value);
}

// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
Expand All @@ -299,6 +347,10 @@ export default class SettingsStore {
* @return {*} The value, or null if not found
*/
static getValue(settingName, roomId = null, excludeDefault = false) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.getValue(settingName, roomId, excludeDefault);
}

// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
Expand All @@ -322,6 +374,10 @@ export default class SettingsStore {
* @return {*} The value, or null if not found.
*/
static getValueAt(level, settingName, roomId = null, explicit = false, excludeDefault = false) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.getValueAt(settingName, roomId, explicit, excludeDefault);
}

// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
Expand Down Expand Up @@ -398,6 +454,10 @@ export default class SettingsStore {
*/
/* eslint-enable valid-jsdoc */
static async setValue(settingName, roomId, level, value) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.setValue(settingName, roomId, level, value);
}

// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
Expand Down Expand Up @@ -441,6 +501,10 @@ export default class SettingsStore {
* @return {boolean} True if the user may set the setting, false otherwise.
*/
static canSetValue(settingName, roomId, level) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.canSetValue(settingName, roomId, level);
}

// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
Expand All @@ -458,6 +522,10 @@ export default class SettingsStore {
* @return {boolean} True if the level is supported, false otherwise.
*/
static isLevelSupported(level) {
if (SettingsStore._testFixture) {
return SettingsStore._testFixture.isLevelSupported(level);
}

if (!LEVEL_HANDLERS[level]) return false;
return LEVEL_HANDLERS[level].isSupported();
}
Expand Down
5 changes: 5 additions & 0 deletions src/stores/RoomListStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class RoomListStore extends Store {
}
}

// For unit tests
reset() {
this._init();
}

_init() {
// Initialise state
const defaultLists = {
Expand Down
133 changes: 133 additions & 0 deletions test/stores/RoomListStore-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import peg from '../../src/MatrixClientPeg';
import * as testUtils from '../test-utils';
import RoomListStore from "../../src/stores/RoomListStore";

const dispatch = testUtils.getDispatchForStore(RoomListStore);

describe('RoomListStore', function() {
let clientSandbox;
let settingsRef;

let rooms = [];

beforeEach(function() {
testUtils.beforeEach(this);
clientSandbox = testUtils.stubClient();
settingsRef = testUtils.stubSettings();
peg.get().credentials = {userId: "@test:example.com"};

// Use our own room lookups so we can stub all the things
rooms = [];
peg.get().getRoom = (roomId) => rooms.find((r) => r.roomId === roomId);

// Reset the state of the store
RoomListStore.reset();
});

afterEach(function() {
clientSandbox.restore();
settingsRef.reset();
});

function createRoomWithCategory(category, tags) {
// TODO: Create stubbed room
// TODO: Make the room match the category
// TODO: Figure out how to safely stub static classes without a bunch of hacks
}

describe('order by importance', function() {
// General case for "does it work"
it('sorts rooms by category then by activity', function() {

});

// We set this up as RED, STICKY, RED, RED and see what happens if we add a RED
it('does not move the sticky room when categories change', function() {

});

// We set this up as RED, STICKY, RED, RED and see what happens when we make the later 2 REDs more active
it('does not move the sticky room when activity changes', function() {

});

it('inserts invites into the invites section', function() {

});

it('inserts joins into the recents section', function() {

});

it('inserts leaves into the archived section', function() {

});

it('inserts direct chats into the people section', function() {

});

it('moves rooms to favourites when they are flagged as such', function() {

});

it('moves rooms to low priority when they flagged as such', function() {

});

// Recents -> New tag
it('moves rooms to custom tags when they are tagged', function() {

});

// Recents -> Existing tag
it('moves rooms to existing custom tags when they are tagged', function() {

});

// Existing tag -> Existing tag
it('moves rooms to between tags when the room changes tags', function() {

});

// Existing tag -> New tag
it('moves rooms to new tags when the room changes tags', function() {

});

// Existing tag -> Recents
it('moves rooms to the recents section when they are untagged', function() {

});

it('orders custom tags by the user-defined order', function() {

});

it('duplicates rooms in tags if they are duplicated among tags', function() {

});

it('does not move rooms to custom tags when they are disabled', function() {

});

// GREY -> RED where both GREY and RED still have rooms
it('moves rooms higher in the list when they change categories', function() {

});

// GREY -> RED where RED doesn't currently have any rooms
it('moves rooms higher when they change to categories which previously did not have rooms', function() {

});

it('moves rooms lower when they are marked as read on other devices', function() {

});

it('moves rooms lower when the sticky room changes', function() {

});
});
});
40 changes: 40 additions & 0 deletions test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PropTypes from 'prop-types';
import peg from '../src/MatrixClientPeg';
import dis from '../src/dispatcher';
import jssdk from 'matrix-js-sdk';
import SettingsStore from "../src/settings/SettingsStore";
const MatrixEvent = jssdk.MatrixEvent;

/**
Expand All @@ -29,6 +30,45 @@ export function beforeEach(context) {
console.log(new Array(1 + desc.length).join("="));
}

/**
* Stubs out the SettingsStore with a fresh instance of a test SettingsStore.
* Using the SettingsStore after this is called will result in the test fixture
* being used. Call `reset()` on the returned object to reset to using the normal
* SettingsStore as it was intended.
*
* @returns {object} an object with a single function, reset, to return the SettingsStore
* to normal.
*/
export function stubSettings() {
SettingsStore.testFixture = createTestSettingsStore();
return {reset: () => SettingsStore.testFixture = null};
}

/**
* Creates a SettingsStore which doesn't respect levels but does track which settings
* are configured.
*
* @returns {object} A mostly-stubbed SettingsStore implementation.
*/
export function createTestSettingsStore() {
const settings = {};
return {
watchSetting: sinon.stub().returns("fake_ref"),
unwatchSetting: sinon.stub(),
monitorSetting: sinon.stub(),
getDisplayName: (settingName) => settings[settingName] ? settingName : null,
getLabsFeatures: sinon.stub().returns([]),
isFeature: (settingName) => !!settings[settingName],
isFeatureEnabled: (settingName) => settings[settingName],
setFeatureEnabled: (settingName, val) => settings[settingName] = val,
getValue: (settingName) => settings[settingName],
getValueAt: (settingName) => settings[settingName],
setValue: (settingName, val) => settings[settingName] = val,
canSetValue: sinon.stub().returns(true),
isLevelSupported: sinon.stub().returns(true),
};
}


/**
* Stub out the MatrixClient, and configure the MatrixClientPeg object to
Expand Down

0 comments on commit b7db917

Please sign in to comment.