Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
69 changes: 69 additions & 0 deletions framework/core/js/src/common/extenders/Conditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type Application from '../Application';
import IExtender, { IExtensionModule } from './IExtender';

export default class Conditional implements IExtender {
protected conditions: { condition: boolean | ((app: Application) => boolean); extenders: () => IExtender<any>[] }[] = [];

/**
* Apply extenders only if an extension is enabled.
*
* Only extensions with JS for the current frontend can be detected.
*
* @param extensionId The ID of the extension.
* @param extenders A callback returning an array of extenders.
*
* @example
* new Extend.Conditional()
* .whenExtensionEnabled('flarum-tags', () => [
* new Extend.Model(Discussion).hasOne<Tag>('acmeFeaturedTag'),
* ])
*/
public whenExtensionEnabled(extensionId: string, extenders: () => IExtender<any>[]): this {
return this.when(() => extensionId in flarum.extensions, extenders);
}

/**
* Apply extenders only if an extension is disabled.
*
* @param extensionId The ID of the extension.
* @param extenders A callback returning an array of extenders.
*
* @example
* new Extend.Conditional()
* .whenExtensionDisabled('flarum-tags', () => [
* new Extend.Model(Discussion).attribute<string>('acmeCategoryLabel'),
* ])
*/
public whenExtensionDisabled(extensionId: string, extenders: () => IExtender<any>[]): this {
return this.when(() => !(extensionId in flarum.extensions), extenders);
}

/**
* Apply extenders based on a condition.
*
* @param condition A boolean, or a callback returning a boolean.
* @param extenders A callback returning an array of extenders.
*
* @example
* new Extend.Conditional()
* .when(
* (app) => !!app.forum.attribute('acmeAdvancedMode'),
* () => [new Extend.Routes().add('acme.advanced', '/acme/advanced', AdvancedPage)]
* )
*/
public when(condition: boolean | ((app: Application) => boolean), extenders: () => IExtender<any>[]): this {
this.conditions.push({ condition, extenders });

return this;
}

extend(app: Application, extension: IExtensionModule): void {
for (const { condition, extenders } of this.conditions) {
if (typeof condition === 'function' ? condition(app) : condition) {
for (const extender of extenders()) {
extender.extend(app, extension);
}
}
}
}
}
2 changes: 2 additions & 0 deletions framework/core/js/src/common/extenders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Search from './Search';
import Notification from './Notification';
import ThemeMode from './ThemeMode';
import Admin from './Admin';
import Conditional from './Conditional';

const extenders = {
Model,
Expand All @@ -16,6 +17,7 @@ const extenders = {
Notification,
ThemeMode,
Admin,
Conditional,
};

export default extenders;
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { jest } from '@jest/globals';
import bootstrapForum from '@flarum/jest-config/src/bootstrap/forum';
import Extend from '../../../../src/common/extenders';
import app from '@flarum/core/src/forum/app';

beforeAll(() => bootstrapForum());

afterEach(() => {
delete flarum.extensions['acme-other'];
});

describe('Conditional extender', () => {
const routes = (name: string) => () => [new Extend.Routes().add(name, `/${name}`, null)];

test('extenders apply when the condition is true', () => {
app.bootExtensions({
test: {
extend: [new Extend.Conditional().when(true, routes('whenTrue'))],
},
});

app.boot();

expect(app.routes).toHaveProperty('whenTrue');
});

test('extenders do not apply when the condition is false', () => {
const extenders = jest.fn(routes('whenFalse'));

app.bootExtensions({
test: {
extend: [new Extend.Conditional().when(() => false, extenders)],
},
});

app.boot();

expect(app.routes).not.toHaveProperty('whenFalse');
expect(extenders).not.toHaveBeenCalled();
});

test('extenders apply when an extension is enabled', () => {
flarum.extensions['acme-other'] = {};

app.bootExtensions({
test: {
extend: [new Extend.Conditional().whenExtensionEnabled('acme-other', routes('enabled'))],
},
});

app.boot();

expect(app.routes).toHaveProperty('enabled');
});

test('extenders do not apply when an extension is disabled', () => {
app.bootExtensions({
test: {
extend: [new Extend.Conditional().whenExtensionEnabled('acme-other', routes('notEnabled'))],
},
});

app.boot();

expect(app.routes).not.toHaveProperty('notEnabled');
});

test('extenders apply when an extension is disabled and expected to be', () => {
app.bootExtensions({
test: {
extend: [new Extend.Conditional().whenExtensionDisabled('acme-other', routes('disabled'))],
},
});

app.boot();

expect(app.routes).toHaveProperty('disabled');
});
});
Loading