Files
mostlymatter/webapp/channels/src/utils/plugins/plugin_setting_extraction.test.tsx
Claudio Costa e5842e67a8 [MM-51514] Custom user settings (#27535)
* Custom user settings

* Implement error boundary, fix validation

* Extraction tests

* Tests

* Bring back refresh option for failed pluggable

* [MM-51514] Allow plugins to open user settings modal (#27742)

* Allow plugins to open user settings modal

* [MM-51514] Improvements to custom plugin user settings (#27754)

* Allow compass icon to be used

* Pass props to custom plugin settings

* Export react-select

* Revert "Export react-select"

This reverts commit 6b026c4eb789d298b721494a70e0e860a704d428.

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2024-08-13 08:49:12 +02:00

495 строки
18 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {PluginConfiguration, PluginConfigurationRadioSetting, PluginConfigurationSection} from 'types/plugins/user_settings';
import {extractPluginConfiguration} from './plugin_setting_extraction';
function getFullExample(): PluginConfiguration {
return {
id: '',
action: {
buttonText: 'some button text',
onClick: () => 1,
text: 'some text',
title: 'some title',
},
sections: [
{
settings: [
{
default: '1-1',
name: '1-1 name',
options: [
{
text: '1-1-1',
value: '1-1-1 value',
helpText: '1-1-1 help text',
},
{
text: '1-1-2',
value: '1-1-2 value',
},
],
type: 'radio',
helpText: '1-1 help text',
title: '1-1 title',
},
{
default: '1-2',
name: '1-2 name',
options: [
{
text: '1-2-1',
value: '1-2-1 value',
helpText: '1-2-1 help text',
},
],
type: 'radio',
},
],
title: 'title 1',
onSubmit: () => 1,
},
{
settings: [
{
default: '2-1',
name: '2-1 name',
options: [
{
text: '2-1-1',
value: '2-1-1 value',
helpText: '2-1-1 help text',
},
{
text: '2-1-2',
value: '2-1-2 value',
},
],
type: 'radio',
helpText: '2-1 help text',
title: '2-1 title',
},
],
title: 'title 2',
onSubmit: () => 2,
disabled: true,
},
],
uiName: 'some name',
icon: 'some icon',
};
}
function CustomSection() {
return (
<div>{'Custom Section'}</div>
);
}
function CustomInput() {
return (
<input>{'Custom Input'}</input>
);
}
function getCustomExample(): PluginConfiguration {
return {
id: '',
uiName: 'some name',
sections: [
{
title: 'Section',
settings: [
{
name: 'radioA',
options: [
{
text: 'Enabled',
value: 'on',
},
{
text: 'Disabled',
value: 'off',
},
],
type: 'radio',
default: 'off',
},
],
},
{
title: 'Section with custom setting',
settings: [
{
name: 'custom_input',
type: 'custom',
component: CustomInput,
},
],
},
{
title: 'Custom section',
component: CustomSection,
},
],
};
}
describe('plugin setting extraction', () => {
beforeAll(() => {
console.warn = jest.fn();
});
it('happy path', () => {
const config = getFullExample();
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(console.warn).not.toBeCalled();
expect(res?.sections).toHaveLength(2);
const sections = res?.sections as PluginConfigurationSection[];
expect(sections[0].settings).toHaveLength(2);
expect(sections[1].settings).toHaveLength(1);
const setting = sections[0].settings[0] as PluginConfigurationRadioSetting;
expect(setting.options).toHaveLength(2);
});
it('id gets overridden', () => {
const config: any = getFullExample();
const pluginId = 'PluginId';
config.id = 'otherId';
let res = extractPluginConfiguration(config, pluginId);
expect(res).toBeTruthy();
expect(res!.id).toBe(pluginId);
delete config.id;
res = extractPluginConfiguration(config, pluginId);
expect(res).toBeTruthy();
expect(res!.id).toBe(pluginId);
});
it('action gets properly added', () => {
const config = getFullExample();
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.action).toBeTruthy();
expect(res?.action?.buttonText).toBe(config.action?.buttonText);
expect(res?.action?.text).toBe(config.action?.text);
expect(res?.action?.title).toBe(config.action?.title);
expect(res?.action?.onClick).toBe(config.action?.onClick);
});
it('sections get properly added', () => {
const config = getFullExample();
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(2);
const sections = res?.sections as PluginConfigurationSection[];
const configSections = config.sections as PluginConfigurationSection[];
expect(sections[0].disabled).toBe(configSections[0].disabled);
expect(sections[0].title).toBe(configSections[0].title);
expect(sections[0].onSubmit).toBe(configSections[0].onSubmit);
expect(sections[0].settings).toHaveLength(configSections[0].settings.length);
expect(sections[1].disabled).toBe(configSections[1].disabled);
expect(sections[1].title).toBe(configSections[1].title);
expect(sections[1].onSubmit).toBe(configSections[1].onSubmit);
expect(sections[1].settings).toHaveLength(configSections[1].settings.length);
});
it('reject configs without name', () => {
const config: any = getFullExample();
config.uiName = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeFalsy();
delete config.uiName;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeFalsy();
});
it('filter out sections without a title', () => {
const config: any = getFullExample();
config.sections[0].title = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(1);
delete config.sections[0].title;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(1);
});
it('filter out settings without a type', () => {
const config: any = getFullExample();
config.sections[0].settings[0].type = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
const sections = res?.sections as PluginConfigurationSection[];
expect(sections[0].settings).toHaveLength(1);
delete config.sections[0].settings[0].type;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(sections[0].settings).toHaveLength(1);
});
it('filter out settings without a name', () => {
const config: any = getFullExample();
config.sections[0].settings[0].name = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
let sections = res?.sections as PluginConfigurationSection[];
expect(sections[0].settings).toHaveLength(1);
delete config.sections[0].settings[0].name;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
sections = res?.sections as PluginConfigurationSection[];
expect(sections[0].settings).toHaveLength(1);
});
it('filter out settings without a default value', () => {
const config: any = getFullExample();
config.sections[0].settings[0].default = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
let sections = res?.sections as PluginConfigurationSection[];
expect(sections[0].settings).toHaveLength(1);
delete config.sections[0].settings[0].default;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
sections = res?.sections as PluginConfigurationSection[];
expect(sections[0].settings).toHaveLength(1);
});
it('filter out radio options without a text', () => {
const config: any = getFullExample();
config.sections[0].settings[0].options[0].text = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
let sections = res?.sections as PluginConfigurationSection[];
let settings = sections[0].settings as PluginConfigurationRadioSetting[];
expect(settings[0].options).toHaveLength(1);
delete config.sections[0].settings[0].options[0].text;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
sections = res?.sections as PluginConfigurationSection[];
settings = sections[0].settings as PluginConfigurationRadioSetting[];
expect(settings[0].options).toHaveLength(1);
});
it('filter out radio options without a value', () => {
const config: any = getFullExample();
config.sections[0].settings[0].options[0].value = '';
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
let sections = res?.sections as PluginConfigurationSection[];
let settings = sections[0].settings as PluginConfigurationRadioSetting[];
expect(settings[0].options).toHaveLength(1);
delete config.sections[0].settings[0].options[0].value;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
sections = res?.sections as PluginConfigurationSection[];
settings = sections[0].settings as PluginConfigurationRadioSetting[];
expect(settings[0].options).toHaveLength(1);
});
it('reject configs without valid sections', () => {
const config = getFullExample();
config.sections = [{
settings: [],
title: 'foo',
}];
const res: any = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeFalsy();
});
it('filter out invalid sections', () => {
const config = getFullExample();
config.sections.push({settings: [], title: 'foo'});
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(2);
});
it('filter out sections without valid settings', () => {
const config = getFullExample();
if ('settings' in config.sections[0] && 'options' in config.sections[0].settings[0]) {
config.sections[0].settings[0].options = [];
}
if ('settings' in config.sections[0] && 'options' in config.sections[0].settings[1]) {
config.sections[0].settings[1].options = [];
}
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(1);
});
it('filter out invalid settings', () => {
const config = getFullExample();
if ('settings' in config.sections[0] && 'options' in config.sections[0].settings[0]) {
config.sections[0].settings[0].options = [];
}
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect((res?.sections[0] as PluginConfigurationSection).settings).toHaveLength(1);
});
it('filter out radio settings without valid options', () => {
const config = getFullExample();
if ('settings' in config.sections[0] && 'options' in config.sections[0].settings[0]) {
config.sections[0].settings[0].options[0].value = '';
config.sections[0].settings[0].options[1].value = '';
}
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect((res?.sections[0] as PluginConfigurationSection).settings).toHaveLength(1);
});
it('filter out ill defined action', () => {
let config: any = getFullExample();
delete config.action?.buttonText;
let res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.action).toBeFalsy();
config = getFullExample();
delete config.action?.title;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.action).toBeFalsy();
config = getFullExample();
delete config.action?.text;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.action).toBeFalsy();
config = getFullExample();
delete config.action?.onClick;
res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.action).toBeFalsy();
});
it('(future proof) filter out extra config arguments', () => {
const config: any = getFullExample();
config.futureProperty = 'hello';
const res: any = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res.futureProperty).toBeFalsy();
});
it('(future proof) filter out extra section arguments', () => {
const config: any = getFullExample();
config.sections[0].futureProperty = 'hello';
const res: any = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res.sections).toHaveLength(2);
expect(res.sections[0].futureProperty).toBeFalsy();
});
it('(future proof) filter out extra setting arguments', () => {
const config: any = getFullExample();
config.sections[0].settings[0].futureProperty = 'hello';
const res: any = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res.sections[0].settings).toHaveLength(2);
expect(res.sections[0].settings[0].futureProperty).toBeFalsy();
});
it('(future proof) filter out extra option arguments', () => {
const config: any = getFullExample();
config.sections[0].settings[0].options[0].futureProperty = 'hello';
const res: any = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res.sections[0].settings[0].options).toHaveLength(2);
expect(res.sections[0].settings[0].options[0].futureProperty).toBeFalsy();
});
it('(future proof) filter out unknown settings', () => {
const config: any = getFullExample();
config.sections[0].settings[0].type = 'newType';
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect((res?.sections[0] as PluginConfigurationSection).settings).toHaveLength(1);
});
describe('custom components', () => {
it('valid', () => {
const config = getCustomExample();
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(3);
expect(console.warn).not.toBeCalled();
});
it('missing setting component', () => {
const config: any = getCustomExample();
delete config.sections[1].settings[0].component;
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(2);
expect(console.warn).toBeCalled();
expect(res?.sections[0].title).toEqual('Section');
expect(res?.sections[1].title).toEqual('Custom section');
});
it('missing section component', () => {
const config: any = getCustomExample();
delete config.sections[2].component;
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(2);
expect(console.warn).toBeCalled();
expect(res?.sections[0].title).toEqual('Section');
expect(res?.sections[1].title).toEqual('Section with custom setting');
});
it('invalid setting component', () => {
const config: any = getCustomExample();
config.sections[1].settings[0].component = (<div/>); // Not a component but an element
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(2);
expect(console.warn).toBeCalled();
expect(res?.sections[0].title).toEqual('Section');
expect(res?.sections[1].title).toEqual('Custom section');
});
it('invalid section component', () => {
const config: any = getCustomExample();
config.sections[2].component = (<div/>); // Not a component but an element
const res = extractPluginConfiguration(config, 'PluginId');
expect(res).toBeTruthy();
expect(res?.sections).toHaveLength(2);
expect(console.warn).toBeCalled();
expect(res?.sections[0].title).toEqual('Section');
expect(res?.sections[1].title).toEqual('Section with custom setting');
});
});
});