Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions webapp/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Icon from './components/icon.jsx';
import PostTypeWebex from './components/post_type_webex';
import {startMeeting} from './actions';
import Client from './client';
import {getServerRoute} from './selectors';

const {id: pluginId} = manifest;

Expand Down Expand Up @@ -39,18 +40,3 @@ class Plugin {
}

window.registerPlugin(pluginId, new Plugin());

const getServerRoute = (state) => {
const config = getConfig(state);

let basePath = '';
if (config && config.SiteURL) {
basePath = new URL(config.SiteURL).pathname;

if (basePath && basePath[basePath.length - 1] === '/') {
basePath = basePath.substr(0, basePath.length - 1);
}
}

return basePath;
};
80 changes: 80 additions & 0 deletions webapp/src/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* @jest-environment jsdom
*/

/* eslint-disable max-nested-callbacks */

jest.mock('@emotion/react', () => ({
jsx: (type, props) => ({type, props}),
}));

jest.mock('./components/icon.jsx', () => ({__esModule: true, default: () => null}));
jest.mock('./components/post_type_webex', () => ({__esModule: true, default: () => null}));

jest.mock('./actions', () => ({
startMeeting: jest.fn(() => jest.fn()),
}));

jest.mock('./client', () => ({
__esModule: true,
default: {
setServerRoute: jest.fn(),
},
}));

jest.mock('mattermost-redux/selectors/entities/general', () => ({
getConfig: jest.fn(() => ({SiteURL: 'https://example.com'})),
}));

jest.mock('./selectors', () => ({
getServerRoute: jest.fn(() => ''),
}));

describe('Plugin initialization', () => {
let mockRegistry;
let mockStore;

beforeEach(() => {
jest.clearAllMocks();

mockRegistry = {
registerChannelHeaderButtonAction: jest.fn(),
registerAppBarComponent: jest.fn(),
registerPostTypeComponent: jest.fn(),
};
mockStore = {
getState: jest.fn(() => ({})),
dispatch: jest.fn(),
};

// Mirror modern Mattermost: window.registerPlugin invokes initialize()
// synchronously during registration. If any module-scoped identifier
// used inside initialize() is still in the temporal dead zone at this
// point (e.g. a const arrow declared after registerPlugin), requiring
// ./index will throw a ReferenceError.
window.registerPlugin = jest.fn((_pluginId, plugin) => {
plugin.initialize(mockRegistry, mockStore);
});
});

test('module evaluation with synchronous initialize() does not throw', () => {
expect(() => {
jest.isolateModules(() => {
require('./index'); // eslint-disable-line global-require
});
}).not.toThrow();
});

test('registers channel header button, app bar icon, and post type component', () => {
jest.isolateModules(() => {
require('./index'); // eslint-disable-line global-require
});

expect(mockRegistry.registerChannelHeaderButtonAction).toHaveBeenCalledTimes(1);
expect(mockRegistry.registerAppBarComponent).toHaveBeenCalledTimes(1);
expect(mockRegistry.registerPostTypeComponent).toHaveBeenCalledWith(
'custom_webex',
expect.anything(),
);
});
});
20 changes: 20 additions & 0 deletions webapp/src/selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {getConfig} from 'mattermost-redux/selectors/entities/general';

export const getServerRoute = (state) => {
const config = getConfig(state);

let basePath = '';
if (config && config.SiteURL) {
try {
basePath = new URL(config.SiteURL).pathname;

if (basePath && basePath[basePath.length - 1] === '/') {
basePath = basePath.substr(0, basePath.length - 1);
}
} catch (e) {
basePath = '';
}
}
Comment on lines +7 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to wrap this in a try-catch block to prevent any possible runtime errors due to a malformed SiteURL

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@avasconcelos114 I wrapped this in a try/catch block.


return basePath;
};
Comment on lines +3 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to have a test suite for this selector specifically as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@avasconcelos114 I added selector tests.

48 changes: 48 additions & 0 deletions webapp/src/selectors.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {getConfig} from 'mattermost-redux/selectors/entities/general';

import {getServerRoute} from './selectors';

jest.mock('mattermost-redux/selectors/entities/general', () => ({
getConfig: jest.fn(),
}));

describe('getServerRoute', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('returns empty string when config is missing', () => {
getConfig.mockReturnValue(null);
expect(getServerRoute({})).toBe('');
});

test('returns empty string when SiteURL is missing', () => {
getConfig.mockReturnValue({});
expect(getServerRoute({})).toBe('');
});

test('returns empty string when SiteURL has no subpath', () => {
getConfig.mockReturnValue({SiteURL: 'https://example.com'});
expect(getServerRoute({})).toBe('');
});

test('returns pathname when SiteURL has a subpath', () => {
getConfig.mockReturnValue({SiteURL: 'https://example.com/mattermost'});
expect(getServerRoute({})).toBe('/mattermost');
});

test('strips trailing slash from subpath', () => {
getConfig.mockReturnValue({SiteURL: 'https://example.com/mattermost/'});
expect(getServerRoute({})).toBe('/mattermost');
});

test('returns empty string when SiteURL is a bare root URL with trailing slash', () => {
getConfig.mockReturnValue({SiteURL: 'https://example.com/'});
expect(getServerRoute({})).toBe('');
});

test('returns empty string when SiteURL is malformed', () => {
getConfig.mockReturnValue({SiteURL: 'not a valid url'});
expect(getServerRoute({})).toBe('');
});
});
Loading