mirror of
https://github.com/tinymce/tinymce.git
synced 2025-07-21 11:32:37 +00:00
TINY-12001: Add TinyMCE 8 license key implementation (#10421)
This commit is contained in:
6
.changes/unreleased/tinymce-TINY-12001-2025-06-25.yaml
Normal file
6
.changes/unreleased/tinymce-TINY-12001-2025-06-25.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
project: tinymce
|
||||
kind: Added
|
||||
body: Support for TinyMCE 8 license keys and license key manager.
|
||||
time: 2025-06-25T10:02:01.705807+10:00
|
||||
custom:
|
||||
Issue: TINY-12001
|
6
.changes/unreleased/tinymce-TINY-12021-2025-04-09.yaml
Normal file
6
.changes/unreleased/tinymce-TINY-12021-2025-04-09.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
project: tinymce
|
||||
kind: Added
|
||||
body: New `pageUid` and `editorUid` properties to tinymce global and editor instance respectively.
|
||||
time: 2025-04-09T13:00:36.019555+10:00
|
||||
custom:
|
||||
Issue: TINY-12021
|
@ -1,3 +1,5 @@
|
||||
import * as IdUtils from '../util/IdUtils';
|
||||
|
||||
import * as Num from './Num';
|
||||
|
||||
/**
|
||||
@ -13,7 +15,7 @@ import * as Num from './Num';
|
||||
*/
|
||||
let unique = 0;
|
||||
|
||||
export const generate = (prefix: string): string => {
|
||||
const generate = (prefix: string): string => {
|
||||
const date = new Date();
|
||||
const time = date.getTime();
|
||||
const random = Math.floor(Num.random() * 1000000000);
|
||||
@ -22,3 +24,21 @@ export const generate = (prefix: string): string => {
|
||||
|
||||
return prefix + '_' + random + unique + String(time);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a uuidv4 string
|
||||
* In accordance with RFC 4122 (https://datatracker.ietf.org/doc/html/rfc4122)
|
||||
*/
|
||||
const uuidV4 = (): `${string}-${string}-${string}-${string}-${string}` => {
|
||||
|
||||
if (window.isSecureContext) {
|
||||
return window.crypto.randomUUID();
|
||||
} else {
|
||||
return IdUtils.uuidV4String();
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
generate,
|
||||
uuidV4
|
||||
};
|
||||
|
36
modules/katamari/src/main/ts/ephox/katamari/util/IdUtils.ts
Normal file
36
modules/katamari/src/main/ts/ephox/katamari/util/IdUtils.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
const uuidV4Bytes = (): Uint8Array => {
|
||||
const bytes = window.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
// https://tools.ietf.org/html/rfc4122#section-4.1.3
|
||||
// This will first bit mask away the most significant 4 bits (version octet)
|
||||
// then mask in the v4 number we only care about v4 random version at this point so (byte & 0b00001111 | 0b01000000)
|
||||
bytes[6] = bytes[6] & 15 | 64;
|
||||
|
||||
// https://tools.ietf.org/html/rfc4122#section-4.1.1
|
||||
// This will first bit mask away the highest two bits then masks in the highest bit so (byte & 0b00111111 | 0b10000000)
|
||||
// So it will set the Msb0=1 & Msb1=0 described by the "The variant specified in this document." row in the table
|
||||
bytes[8] = bytes[8] & 63 | 128;
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const uuidV4String = (): `${string}-${string}-${string}-${string}-${string}` => {
|
||||
const uuid = uuidV4Bytes();
|
||||
const getHexRange = (startIndex: number, endIndex: number) => {
|
||||
let buff = '';
|
||||
for (let i = startIndex; i <= endIndex; ++i) {
|
||||
const hexByte = uuid[i].toString(16).padStart(2, '0');
|
||||
buff += hexByte;
|
||||
}
|
||||
return buff;
|
||||
};
|
||||
// RFC 4122 UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
return `${getHexRange(0, 3)}-${getHexRange(4, 5)}-${getHexRange(6, 7)}-${getHexRange(8, 9)}-${getHexRange(10, 15)}`;
|
||||
};
|
||||
|
||||
export {
|
||||
uuidV4Bytes,
|
||||
uuidV4String
|
||||
};
|
@ -1,20 +1,62 @@
|
||||
import { describe, it } from '@ephox/bedrock-client';
|
||||
import { describe, it, context } from '@ephox/bedrock-client';
|
||||
import { assert } from 'chai';
|
||||
import fc from 'fast-check';
|
||||
|
||||
import * as Id from 'ephox/katamari/api/Id';
|
||||
import * as IdUtils from 'ephox/katamari/util/IdUtils';
|
||||
|
||||
describe('atomic.katamari.api.data.IdTest', () => () => {
|
||||
it('Unit Tests', () => {
|
||||
const one = Id.generate('test');
|
||||
const two = Id.generate('test');
|
||||
assert.equal(one.indexOf('test'), 0);
|
||||
assert.equal(two.indexOf('test'), 0);
|
||||
assert.notEqual(one, two);
|
||||
describe('atomic.katamari.api.data.IdTest', () => {
|
||||
context('generate', () => {
|
||||
it('Unit Tests', () => {
|
||||
const one = Id.generate('test');
|
||||
const two = Id.generate('test');
|
||||
assert.equal(one.indexOf('test'), 0);
|
||||
assert.equal(two.indexOf('test'), 0);
|
||||
assert.notEqual(one, two);
|
||||
});
|
||||
|
||||
it('should not generate identical IDs', () => {
|
||||
const arbId = fc.string(1, 30).map(Id.generate);
|
||||
fc.assert(fc.property(arbId, arbId, (id1, id2) => id1 !== id2));
|
||||
});
|
||||
});
|
||||
|
||||
it('should not generate identical IDs', () => {
|
||||
const arbId = fc.string(1, 30).map(Id.generate);
|
||||
fc.assert(fc.property(arbId, arbId, (id1, id2) => id1 !== id2));
|
||||
context('uuidV4', () => {
|
||||
const assertIsUuidV4 = (uuid: string): void => {
|
||||
// From https://github.com/uuidjs/uuid/blob/main/src/regex.js
|
||||
const v4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
assert.isString(uuid);
|
||||
assert.lengthOf(uuid, 36);
|
||||
assert.match(uuid, v4Regex);
|
||||
const version = parseInt(uuid.slice(14, 15), 16);
|
||||
assert.equal(version, 4);
|
||||
};
|
||||
|
||||
it('native uuids should be valid and unique', () => {
|
||||
const length = 1000;
|
||||
const uuids = Array.from({ length }, Id.uuidV4);
|
||||
const [ one, two ] = uuids;
|
||||
assertIsUuidV4(one);
|
||||
assertIsUuidV4(two);
|
||||
assert.notStrictEqual(one, two);
|
||||
assert.lengthOf(new Set(uuids), length);
|
||||
});
|
||||
|
||||
it('non-native uuids should be valid and unique', () => {
|
||||
const length = 1000;
|
||||
const uuids = Array.from({ length }, IdUtils.uuidV4String);
|
||||
const [ one, two ] = uuids;
|
||||
assertIsUuidV4(one);
|
||||
assertIsUuidV4(two);
|
||||
assert.notStrictEqual(one, two);
|
||||
assert.lengthOf(new Set(uuids), length);
|
||||
});
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc4122#section-4.4
|
||||
it('uuidV4Bytes should have correct fields set from section 4.4', () => {
|
||||
const bytes = IdUtils.uuidV4Bytes();
|
||||
const bits = bytes[8].toString(2).padStart(8, '0');
|
||||
assert.strictEqual(bits.substring(0, 2), '10');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -109,6 +109,7 @@ export default (): void => {
|
||||
},
|
||||
image_caption: true,
|
||||
theme: 'silver',
|
||||
license_key: 'gpl',
|
||||
setup: (ed) => {
|
||||
makeSidebar(ed, 'sidebar1', 'green', 200);
|
||||
makeSidebar(ed, 'sidebar2', 'green', 200);
|
||||
|
@ -53,6 +53,10 @@ const modelLoadError = (editor: Editor, url: string, name: string): void => {
|
||||
logError(editor, 'ModelLoadError', createLoadError('model', url, name));
|
||||
};
|
||||
|
||||
const licenseKeyManagerLoadError = (editor: Editor, url: string, name: string): void => {
|
||||
logError(editor, 'LicenseKeyManagerLoadError', createLoadError('license key manager', url, name));
|
||||
};
|
||||
|
||||
const pluginInitError = (editor: Editor, name: string, err: any): void => {
|
||||
const message = I18n.translate([ 'Failed to initialize plugin: {0}', name ]);
|
||||
fireError(editor, 'PluginLoadError', { message });
|
||||
@ -77,6 +81,7 @@ export {
|
||||
languageLoadError,
|
||||
themeLoadError,
|
||||
modelLoadError,
|
||||
licenseKeyManagerLoadError,
|
||||
pluginInitError,
|
||||
uploadError,
|
||||
displayError,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Arr, Fun, Type } from '@ephox/katamari';
|
||||
import { Arr, Fun, Type, Id } from '@ephox/katamari';
|
||||
|
||||
import * as EditorContent from '../content/EditorContent';
|
||||
import * as Deprecations from '../Deprecations';
|
||||
@ -6,6 +6,7 @@ import * as NodeType from '../dom/NodeType';
|
||||
import * as EditorRemove from '../EditorRemove';
|
||||
import { BlobInfoImagePair } from '../file/ImageScanner';
|
||||
import * as EditorFocus from '../focus/EditorFocus';
|
||||
import { LicenseKeyManager } from '../init/LicenseKeyManager';
|
||||
import * as Render from '../init/Render';
|
||||
import { type UserLookup, createUserLookup } from '../lookup/UserLookup';
|
||||
import * as EditableRoot from '../mode/EditableRoot';
|
||||
@ -98,6 +99,14 @@ class Editor implements EditorObservable {
|
||||
*/
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* A uuid string to uniquely identify an editor across any page.
|
||||
*
|
||||
* @property editorUid
|
||||
* @type String
|
||||
*/
|
||||
public editorUid: string;
|
||||
|
||||
/**
|
||||
* Name/Value object containing plugin instances.
|
||||
*
|
||||
@ -243,6 +252,7 @@ class Editor implements EditorObservable {
|
||||
public model!: Model;
|
||||
public undoManager!: UndoManager;
|
||||
public windowManager!: WindowManager;
|
||||
public licenseKeyManager!: LicenseKeyManager;
|
||||
public _beforeUnload: (() => void) | undefined;
|
||||
public _eventDispatcher: EventDispatcher<NativeEventMap> | undefined;
|
||||
public _nodeChangeDispatcher!: NodeChange;
|
||||
@ -279,6 +289,7 @@ class Editor implements EditorObservable {
|
||||
const self = this;
|
||||
|
||||
this.id = id;
|
||||
this.editorUid = Id.uuidV4();
|
||||
this.hidden = false;
|
||||
const normalizedOptions = normalizeOptions(editorManager.defaultOptions, options);
|
||||
|
||||
@ -350,6 +361,13 @@ class Editor implements EditorObservable {
|
||||
|
||||
this.mode = createMode(self);
|
||||
|
||||
// Lock certain properties to reduce misuse
|
||||
Object.defineProperty(this, 'editorUid', {
|
||||
writable: false,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
// Call setup
|
||||
editorManager.dispatch('SetupEditor', { editor: this });
|
||||
const setupCallback = Options.getSetupCallback(self);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Arr, Obj, Type } from '@ephox/katamari';
|
||||
import { Arr, Obj, Type, Id } from '@ephox/katamari';
|
||||
|
||||
import * as ErrorReporter from '../ErrorReporter';
|
||||
import * as FocusController from '../focus/FocusController';
|
||||
import LicenseKeyManagerLoader, { LicenseKeyManagerAddon } from '../init/LicenseKeyManager';
|
||||
|
||||
import AddOnManager from './AddOnManager';
|
||||
import DOMUtils from './dom/DOMUtils';
|
||||
@ -100,6 +101,7 @@ interface EditorManager extends Observable<EditorManagerEventMap> {
|
||||
documentBaseURL: string;
|
||||
i18n: I18n;
|
||||
suffix: string;
|
||||
pageUid: string;
|
||||
|
||||
add (this: EditorManager, editor: Editor): Editor;
|
||||
addI18n: (code: string, item: Record<string, string>) => void;
|
||||
@ -118,6 +120,7 @@ interface EditorManager extends Observable<EditorManagerEventMap> {
|
||||
translate: (text: Untranslated) => TranslatedString;
|
||||
triggerSave: () => void;
|
||||
_setBaseUrl (this: EditorManager, baseUrl: string): void;
|
||||
_addLicenseKeyManager (this: EditorManager, addOn: LicenseKeyManagerAddon): void;
|
||||
}
|
||||
|
||||
const isQuirksMode = document.compatMode !== 'CSS1Compat';
|
||||
@ -139,6 +142,14 @@ const EditorManager: EditorManager = {
|
||||
documentBaseURL: null as any,
|
||||
suffix: null as any,
|
||||
|
||||
/**
|
||||
* A uuid string to anonymously identify the page tinymce is loaded in
|
||||
*
|
||||
* @property pageUid
|
||||
* @type String
|
||||
*/
|
||||
pageUid: Id.uuidV4(),
|
||||
|
||||
/**
|
||||
* Major version of TinyMCE build.
|
||||
*
|
||||
@ -276,6 +287,17 @@ const EditorManager: EditorManager = {
|
||||
self.suffix = suffix;
|
||||
|
||||
FocusController.setup(self);
|
||||
|
||||
// Lock certain properties to reduce misuse
|
||||
Arr.each(
|
||||
[ 'majorVersion', 'minorVersion', 'releaseDate', 'pageUid', '_addLicenseKeyManager' ],
|
||||
(property) =>
|
||||
Object.defineProperty(self, property, {
|
||||
writable: false,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -736,7 +758,9 @@ const EditorManager: EditorManager = {
|
||||
_setBaseUrl(baseUrl: string) {
|
||||
this.baseURL = new URI(this.documentBaseURL).toAbsolute(baseUrl.replace(/\/+$/, ''));
|
||||
this.baseURI = new URI(this.baseURL);
|
||||
}
|
||||
},
|
||||
|
||||
_addLicenseKeyManager: (addOn: LicenseKeyManagerAddon) => LicenseKeyManagerLoader.add(addOn),
|
||||
};
|
||||
|
||||
EditorManager.setup();
|
||||
|
@ -20,6 +20,7 @@ import * as Disabled from '../mode/Disabled';
|
||||
import { appendContentCssFromSettings } from './ContentCss';
|
||||
import * as InitContentBody from './InitContentBody';
|
||||
import * as InitIframe from './InitIframe';
|
||||
import LicenseKeyManagerLoader from './LicenseKeyManager';
|
||||
|
||||
const DOM = DOMUtils.DOM;
|
||||
|
||||
@ -122,6 +123,10 @@ const initModel = (editor: Editor) => {
|
||||
editor.model = Model(editor, ModelManager.urls[model]);
|
||||
};
|
||||
|
||||
const initLicenseKeyManager = (editor: Editor) => {
|
||||
LicenseKeyManagerLoader.init(editor);
|
||||
};
|
||||
|
||||
const renderFromLoadedTheme = (editor: Editor) => {
|
||||
// Render UI
|
||||
const render = editor.theme.renderUI;
|
||||
@ -203,6 +208,7 @@ const init = async (editor: Editor): Promise<void> => {
|
||||
initTooltipClosing(editor);
|
||||
initTheme(editor);
|
||||
initModel(editor);
|
||||
initLicenseKeyManager(editor);
|
||||
initPlugins(editor);
|
||||
const renderInfo = await renderThemeUi(editor);
|
||||
augmentEditorUiApi(editor, Optional.from(renderInfo.api).getOr({}));
|
||||
|
@ -40,7 +40,6 @@ import * as TextPattern from '../textpatterns/TextPatterns';
|
||||
import Quirks from '../util/Quirks';
|
||||
|
||||
import * as ContentCss from './ContentCss';
|
||||
import * as LicenseKeyValidation from './LicenseKeyValidation';
|
||||
|
||||
declare const escape: any;
|
||||
declare let tinymce: TinyMCE;
|
||||
@ -489,8 +488,6 @@ const contentBodyLoaded = (editor: Editor): void => {
|
||||
|
||||
preInit(editor);
|
||||
|
||||
LicenseKeyValidation.validateEditorLicenseKey(editor);
|
||||
|
||||
setupRtcThunk.fold(() => {
|
||||
const cancelProgress = startProgress(editor);
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
107
modules/tinymce/src/core/main/ts/init/LicenseKeyManager.ts
Normal file
107
modules/tinymce/src/core/main/ts/init/LicenseKeyManager.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { Fun, Obj, Type } from '@ephox/katamari';
|
||||
|
||||
import AddOnManager, { AddOnConstructor } from '../api/AddOnManager';
|
||||
import Editor from '../api/Editor';
|
||||
import * as Options from '../api/Options';
|
||||
import * as ErrorReporter from '../ErrorReporter';
|
||||
|
||||
export type LicenseKeyManagerAddon = AddOnConstructor<LicenseKeyManager>;
|
||||
|
||||
interface LicenseKeyManagerLoader {
|
||||
readonly load: (editor: Editor, suffix: string) => void;
|
||||
readonly isLoaded: (editor: Editor) => boolean;
|
||||
readonly add: (addOn: LicenseKeyManagerAddon) => void;
|
||||
readonly init: (editor: Editor) => void;
|
||||
}
|
||||
|
||||
interface ValidateData {
|
||||
plugin?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface LicenseKeyManager {
|
||||
readonly validate: (data: ValidateData) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const GplLicenseKeyManager: LicenseKeyManager = {
|
||||
validate: (data) => {
|
||||
const { plugin } = data;
|
||||
// Premium plugins are not allowed if 'gpl' is given as the license_key
|
||||
return Promise.resolve(!Type.isString(plugin));
|
||||
},
|
||||
};
|
||||
|
||||
const ADDON_KEY = 'manager';
|
||||
const PLUGIN_CODE = 'licensekeymanager';
|
||||
|
||||
const setup = (): LicenseKeyManagerLoader => {
|
||||
const addOnManager = AddOnManager<LicenseKeyManager>();
|
||||
|
||||
const add = (addOn: LicenseKeyManagerAddon): void => {
|
||||
addOnManager.add(ADDON_KEY, addOn);
|
||||
};
|
||||
|
||||
const load = (editor: Editor, suffix: string): void => {
|
||||
const licenseKey = Options.getLicenseKey(editor);
|
||||
const plugins = new Set(Options.getPlugins(editor));
|
||||
const hasApiKey = Type.isString(Options.getApiKey(editor));
|
||||
|
||||
// Early return if addonConstructor already exists
|
||||
if (Obj.has(addOnManager.urls, ADDON_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try loading external license key manager when
|
||||
// - license_key is not 'gpl'; or
|
||||
// - an API key is present; or
|
||||
// - the licensekeymanager has been explicity listed, most likely through the forced_plugins option
|
||||
if (licenseKey?.toLowerCase() !== 'gpl' || hasApiKey || plugins.has(PLUGIN_CODE)) {
|
||||
const url = `plugins/${PLUGIN_CODE}/plugin${suffix}.js`;
|
||||
addOnManager.load(ADDON_KEY, url).catch(() => {
|
||||
ErrorReporter.licenseKeyManagerLoadError(editor, url, ADDON_KEY);
|
||||
});
|
||||
} else {
|
||||
add(Fun.constant(GplLicenseKeyManager));
|
||||
}
|
||||
};
|
||||
|
||||
const isLoaded = (_editor: Editor): boolean => {
|
||||
return Type.isNonNullable(addOnManager.get(ADDON_KEY));
|
||||
};
|
||||
|
||||
const init = (editor: Editor): void => {
|
||||
const setLicenseKeyManager = (licenseKeyManager: LicenseKeyManager) => {
|
||||
Object.defineProperty(editor, 'licenseKeyManager', {
|
||||
value: licenseKeyManager,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
});
|
||||
};
|
||||
|
||||
// editor will not load without a license key manager constructor
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const LicenseKeyManager = addOnManager.get(ADDON_KEY)!;
|
||||
|
||||
const licenseKeyManagerApi = LicenseKeyManager(editor, addOnManager.urls[ADDON_KEY]);
|
||||
setLicenseKeyManager(licenseKeyManagerApi);
|
||||
|
||||
// Validation of the license key is done asynchronously and does
|
||||
// not block initialization of the editor
|
||||
// The validate function is expected to set the editor to the correct
|
||||
// state depending on if the license key is valid or not
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
editor.licenseKeyManager.validate({});
|
||||
};
|
||||
|
||||
return {
|
||||
load,
|
||||
isLoaded,
|
||||
add,
|
||||
init
|
||||
};
|
||||
};
|
||||
|
||||
const LicenseKeyManagerLoader: LicenseKeyManagerLoader = setup();
|
||||
|
||||
export default LicenseKeyManagerLoader;
|
@ -1,21 +0,0 @@
|
||||
import { Type } from '@ephox/katamari';
|
||||
|
||||
import Editor from '../api/Editor';
|
||||
import * as Options from '../api/Options';
|
||||
|
||||
type KeyStatus = 'VALID' | 'INVALID';
|
||||
|
||||
const isGplKey = (key: string) => key.toLowerCase() === 'gpl';
|
||||
|
||||
const isValidGeneratedKey = (key: string): boolean => key.length >= 64 && key.length <= 255;
|
||||
|
||||
export const validateLicenseKey = (key: string): KeyStatus => isGplKey(key) || isValidGeneratedKey(key) ? 'VALID' : 'INVALID';
|
||||
|
||||
export const validateEditorLicenseKey = (editor: Editor): void => {
|
||||
const licenseKey = Options.getLicenseKey(editor);
|
||||
const hasApiKey = Type.isString(Options.getApiKey(editor));
|
||||
if (!hasApiKey && (Type.isUndefined(licenseKey) || validateLicenseKey(licenseKey) === 'INVALID')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`TinyMCE is running in evaluation mode. Provide a valid license key or add license_key: 'gpl' to the init config to agree to the open source license terms. Read more at https://www.tiny.cloud/license-key/`);
|
||||
}
|
||||
};
|
@ -20,6 +20,7 @@ import * as StyleSheetLoaderRegistry from '../dom/StyleSheetLoaderRegistry';
|
||||
import * as ErrorReporter from '../ErrorReporter';
|
||||
|
||||
import * as Init from './Init';
|
||||
import LicenseKeyManagerLoader from './LicenseKeyManager';
|
||||
|
||||
interface UrlMeta {
|
||||
readonly url: string;
|
||||
@ -68,6 +69,10 @@ const loadModel = (editor: Editor, suffix: string): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadLicenseKeyManager = (editor: Editor, suffix: string): void => {
|
||||
LicenseKeyManagerLoader.load(editor, suffix);
|
||||
};
|
||||
|
||||
const getIconsUrlMetaFromUrl = (editor: Editor): Optional<UrlMeta> => Optional.from(Options.getIconsUrl(editor))
|
||||
.filter(Strings.isNotEmpty)
|
||||
.map((url) => ({
|
||||
@ -95,6 +100,12 @@ const loadIcons = (scriptLoader: ScriptLoader, editor: Editor, suffix: string) =
|
||||
|
||||
const loadPlugins = (editor: Editor, suffix: string) => {
|
||||
const loadPlugin = (name: string, url: string) => {
|
||||
// If licensekeymanager is included in the plugins list
|
||||
// or through external_plugins, skip it
|
||||
if (name === 'licensekeymanager') {
|
||||
return;
|
||||
}
|
||||
|
||||
PluginManager.load(name, url).catch(() => {
|
||||
ErrorReporter.pluginLoadError(editor, url, name);
|
||||
});
|
||||
@ -124,13 +135,22 @@ const isModelLoaded = (editor: Editor): boolean => {
|
||||
return Type.isNonNullable(ModelManager.get(model));
|
||||
};
|
||||
|
||||
const isLicenseKeyManagerLoaded = (editor: Editor): boolean => {
|
||||
return LicenseKeyManagerLoader.isLoaded(editor);
|
||||
};
|
||||
|
||||
const loadScripts = (editor: Editor, suffix: string) => {
|
||||
const scriptLoader = ScriptLoader.ScriptLoader;
|
||||
|
||||
const initEditor = () => {
|
||||
// If the editor has been destroyed or the theme and model haven't loaded then
|
||||
// If the editor has been destroyed or the theme, model, licenseKeyManager haven't loaded then
|
||||
// don't continue to load the editor
|
||||
if (!editor.removed && isThemeLoaded(editor) && isModelLoaded(editor)) {
|
||||
if (
|
||||
!editor.removed &&
|
||||
isThemeLoaded(editor) &&
|
||||
isModelLoaded(editor) &&
|
||||
isLicenseKeyManagerLoaded(editor)
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Init.init(editor);
|
||||
}
|
||||
@ -138,6 +158,7 @@ const loadScripts = (editor: Editor, suffix: string) => {
|
||||
|
||||
loadTheme(editor, suffix);
|
||||
loadModel(editor, suffix);
|
||||
loadLicenseKeyManager(editor, suffix);
|
||||
loadLanguage(scriptLoader, editor);
|
||||
loadIcons(scriptLoader, editor, suffix);
|
||||
loadPlugins(editor, suffix);
|
||||
|
@ -42,6 +42,7 @@ describe.skip('browser.tinymce.core.EditorAutoFocusTest', () => {
|
||||
EditorManager.init({
|
||||
selector: 'div.tinymce',
|
||||
base_url: '/project/tinymce/js/tinymce/',
|
||||
license_key: 'gpl',
|
||||
menubar: false,
|
||||
statusbar: false,
|
||||
height,
|
||||
|
@ -5,6 +5,7 @@ import { assert } from 'chai';
|
||||
import 'tinymce';
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import EditorManager from 'tinymce/core/api/EditorManager';
|
||||
import Model from 'tinymce/models/dom/Model';
|
||||
import Theme from 'tinymce/themes/silver/Theme';
|
||||
|
||||
import * as ViewBlock from '../module/test/ViewBlock';
|
||||
@ -14,6 +15,7 @@ describe('browser.tinymce.core.EditorManagerCommandsTest', () => {
|
||||
|
||||
before(() => {
|
||||
Theme();
|
||||
Model();
|
||||
EditorManager._setBaseUrl('/project/tinymce/js/tinymce');
|
||||
});
|
||||
|
||||
@ -38,6 +40,7 @@ describe('browser.tinymce.core.EditorManagerCommandsTest', () => {
|
||||
viewBlock.update('<textarea id="ed_1" class="tinymce"></textarea>');
|
||||
await EditorManager.init({
|
||||
selector: 'textarea.tinymce',
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (editor1) => {
|
||||
assert.isFalse(editor1.isHidden(), 'editor should be visible');
|
||||
EditorManager.execCommand('mceToggleEditor', false, test.value);
|
||||
@ -53,6 +56,7 @@ describe('browser.tinymce.core.EditorManagerCommandsTest', () => {
|
||||
viewBlock.update('<textarea id="ed_1" class="tinymce"></textarea>');
|
||||
await EditorManager.init({
|
||||
selector: 'textarea.tinymce',
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (_editor1) => {
|
||||
assert.lengthOf(EditorManager.get(), 1);
|
||||
EditorManager.execCommand('mceRemoveEditor', false, test.value);
|
||||
@ -67,12 +71,14 @@ describe('browser.tinymce.core.EditorManagerCommandsTest', () => {
|
||||
viewBlock.update('<textarea id="ed_1" class="tinymce"></textarea><textarea id="ed_2" class="tinymce"></textarea>');
|
||||
await EditorManager.init({
|
||||
selector: 'textarea#ed_1',
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (_editor1) => {
|
||||
assert.lengthOf(EditorManager.get(), 1);
|
||||
assert.isFalse(EditorManager.get('ed_1')?.mode.isReadOnly());
|
||||
EditorManager.execCommand('mceAddEditor', false, {
|
||||
id: 'ed_2',
|
||||
options: {
|
||||
license_key: 'gpl',
|
||||
readonly: true,
|
||||
init_instance_callback: (editor2: Editor) => {
|
||||
assert.lengthOf(EditorManager.get(), 2);
|
||||
|
@ -33,8 +33,10 @@ describe('browser.tinymce.core.EditorManagerSetupTest', () => {
|
||||
|
||||
it('script baseURL and suffix with script in svg', () => {
|
||||
viewBlock.update('<svg><script>!function(){}();</script></svg><script src="http://localhost/nonexistant/tinymce.min.js" type="application/javascript"></script>');
|
||||
const pageUid = EditorManager.pageUid;
|
||||
EditorManager.setup();
|
||||
assert.equal(EditorManager.baseURL, 'http://localhost/nonexistant', 'BaseURL is interpreted from the script src');
|
||||
assert.equal(EditorManager.suffix, '.min', 'Suffix is interpreted from the script src');
|
||||
assert.strictEqual(EditorManager.pageUid, pageUid, 'pageUid should not change after calling setup');
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { after, afterEach, before, describe, it } from '@ephox/bedrock-client';
|
||||
import { Obj } from '@ephox/katamari';
|
||||
import { Remove, Selectors } from '@ephox/sugar';
|
||||
import { assert } from 'chai';
|
||||
import 'tinymce';
|
||||
@ -8,14 +9,21 @@ import Editor from 'tinymce/core/api/Editor';
|
||||
import EditorManager from 'tinymce/core/api/EditorManager';
|
||||
import PluginManager from 'tinymce/core/api/PluginManager';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
import Model from 'tinymce/models/dom/Model';
|
||||
import Theme from 'tinymce/themes/silver/Theme';
|
||||
|
||||
import * as UuidUtils from '../module/test/UuidUtils';
|
||||
import * as ViewBlock from '../module/test/ViewBlock';
|
||||
|
||||
describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
const viewBlock = ViewBlock.bddSetup();
|
||||
|
||||
before(() => {
|
||||
Theme();
|
||||
Model();
|
||||
EditorManager._setBaseUrl('/project/tinymce/js/tinymce');
|
||||
// Check pageUid is defined before any editors are created
|
||||
UuidUtils.assertIsUuid(EditorManager.pageUid);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
@ -34,6 +42,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
selector: 'textarea.tinymce',
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (editor1) => {
|
||||
assert.lengthOf(EditorManager.get(), 1);
|
||||
assert.equal(EditorManager.get(0), EditorManager.activeEditor);
|
||||
@ -58,6 +67,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
selector: '#' + (EditorManager.activeEditor as Editor).id,
|
||||
license_key: 'gpl',
|
||||
});
|
||||
|
||||
assert.lengthOf(EditorManager.get(), 1);
|
||||
@ -88,6 +98,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
EditorManager.init({
|
||||
selector: 'textarea',
|
||||
language: langCode,
|
||||
license_key: 'gpl',
|
||||
language_url: langUrl,
|
||||
init_instance_callback: (_ed) => {
|
||||
const scripts = Tools.grep(document.getElementsByTagName('script'), (script) => {
|
||||
@ -107,6 +118,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
selector: 'textarea',
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (editor1) => {
|
||||
setTimeout(() => {
|
||||
// Destroy the editor by setting innerHTML common ajax pattern
|
||||
@ -119,6 +131,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
selector: 'textarea',
|
||||
license_key: 'gpl',
|
||||
skin_url: '/project/tinymce/js/tinymce/skins/ui/oxide',
|
||||
content_css: '/project/tinymce/js/tinymce/skins/content/default',
|
||||
init_instance_callback: (editor2) => {
|
||||
@ -163,6 +176,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
|
||||
assert.deepEqual(new Editor('ed2', {
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
license_key: 'gpl',
|
||||
external_plugins: {
|
||||
plugina: '//domain/plugina2.js',
|
||||
pluginc: '//domain/pluginc.js'
|
||||
@ -177,7 +191,8 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
});
|
||||
|
||||
assert.deepEqual(new Editor('ed3', {
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
license_key: 'gpl',
|
||||
}, EditorManager).options.get('external_plugins'), {
|
||||
plugina: '//domain/plugina.js',
|
||||
pluginb: '//domain/pluginb.js'
|
||||
@ -202,6 +217,7 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
selector: invalidName + '.targetEditor',
|
||||
license_key: 'gpl',
|
||||
inline: true
|
||||
});
|
||||
|
||||
@ -209,4 +225,46 @@ describe('browser.tinymce.core.EditorManagerTest', () => {
|
||||
DOMUtils.DOM.remove(elm);
|
||||
});
|
||||
});
|
||||
|
||||
it('TINY-12021: pageUid', async () => {
|
||||
const pageUid = EditorManager.pageUid;
|
||||
UuidUtils.assertIsUuid(pageUid);
|
||||
|
||||
viewBlock.update('<textarea class="tinymce"></textarea><textarea class="tinymce"></textarea>');
|
||||
await EditorManager.init({
|
||||
selector: 'textarea.tinymce',
|
||||
license_key: 'gpl',
|
||||
setup: (editor) => {
|
||||
assert.equal(editor.editorManager.pageUid, pageUid);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('TINY-12020: EditorManager should have correct locked properties', async () => {
|
||||
const lockedEditorManagerProperties = [ 'majorVersion', 'minorVersion', 'releaseDate', 'pageUid', '_addLicenseKeyManager' ] as const;
|
||||
const lockedPropertiesSet = new Set<string>(lockedEditorManagerProperties);
|
||||
|
||||
const descriptors = Object.getOwnPropertyDescriptors(EditorManager);
|
||||
Obj.each(descriptors, (descriptor, key) => {
|
||||
if (lockedPropertiesSet.has(key)) {
|
||||
assert.isFalse(descriptor.configurable, `${key} should not be configurable`);
|
||||
assert.isFalse(descriptor.writable, `${key} should not be writable`);
|
||||
assert.isTrue(descriptor.enumerable, `${key} should be enumerable`);
|
||||
} else {
|
||||
assert.isTrue(descriptor.configurable, `${key} should be configurable`);
|
||||
assert.isTrue(descriptor.writable, `${key} should be writable`);
|
||||
assert.isTrue(descriptor.enumerable, `${key} should be enumerable`);
|
||||
}
|
||||
});
|
||||
|
||||
for (const property of lockedEditorManagerProperties) {
|
||||
assert.throws(() => {
|
||||
(EditorManager[property] as any) = 'some_random_value';
|
||||
});
|
||||
assert.throws(() => {
|
||||
delete EditorManager[property];
|
||||
});
|
||||
assert.notStrictEqual(EditorManager[property], 'some_random_value');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -21,7 +21,7 @@ describe('browser.tinymce.core.EditorRemoveTest', () => {
|
||||
assertTextareaDisplayStyle(editor, 'none');
|
||||
editor.remove();
|
||||
assertTextareaDisplayStyle(editor, expectedStyle);
|
||||
await EditorManager.init({ selector: '#tinymce' });
|
||||
await EditorManager.init({ selector: '#tinymce', license_key: 'gpl' });
|
||||
assertTextareaDisplayStyle(editor, expectedStyle);
|
||||
McEditor.remove(editor);
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { UiFinder, Waiter } from '@ephox/agar';
|
||||
import { context, describe, it } from '@ephox/bedrock-client';
|
||||
import { Fun } from '@ephox/katamari';
|
||||
import { Fun, Obj } from '@ephox/katamari';
|
||||
import { PlatformDetection } from '@ephox/sand';
|
||||
import { Attribute, Class, SugarBody } from '@ephox/sugar';
|
||||
import { TinyAssertions, TinyHooks, TinySelections } from '@ephox/wrap-mcagar';
|
||||
import { McEditor, TinyAssertions, TinyHooks, TinySelections } from '@ephox/wrap-mcagar';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
@ -15,6 +15,7 @@ import URI from 'tinymce/core/api/util/URI';
|
||||
import { UndoLevel } from 'tinymce/core/undo/UndoManagerTypes';
|
||||
|
||||
import * as HtmlUtils from '../module/test/HtmlUtils';
|
||||
import * as UuidUtils from '../module/test/UuidUtils';
|
||||
|
||||
describe('browser.tinymce.core.EditorTest', () => {
|
||||
const browser = PlatformDetection.detect().browser;
|
||||
@ -580,4 +581,25 @@ describe('browser.tinymce.core.EditorTest', () => {
|
||||
checkWithManager('Plugin which has not loaded does not return true', 'Has ParticularPlugin In List', 'ParticularPlugin', false, false);
|
||||
});
|
||||
});
|
||||
|
||||
context('editorUid', () => {
|
||||
it('TINY-12021: should exist and be unique', async () => {
|
||||
const editor = hook.editor();
|
||||
const editor2 = await McEditor.pCreate<Editor>();
|
||||
UuidUtils.assertIsUuid(editor.editorUid);
|
||||
UuidUtils.assertIsUuid(editor2.editorUid);
|
||||
assert.notStrictEqual(editor.editorUid, editor2.editorUid);
|
||||
McEditor.remove(editor2);
|
||||
});
|
||||
|
||||
it('TINY-12020: should be locked', () => {
|
||||
const editor = hook.editor();
|
||||
const keys = new Set(Obj.keys(editor));
|
||||
assert.isTrue(keys.has('editorUid'), `expected editorUid when enumerating editor`);
|
||||
assert.throws(() => {
|
||||
editor.editorUid = 'some_random_value';
|
||||
});
|
||||
assert.notStrictEqual(editor.editorUid, 'some_random_value');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -43,6 +43,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
target: elm1,
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (ed) => {
|
||||
assert.strictEqual(ed.targetElm, elm1);
|
||||
done();
|
||||
@ -57,6 +58,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
target: elm,
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (ed) => {
|
||||
assert.isAbove(ed.id.length, 0, 'editors id set to: ' + ed.id);
|
||||
assert.strictEqual(ed.targetElm, elm);
|
||||
@ -73,6 +75,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
EditorManager.init({
|
||||
selector: '#elm-2',
|
||||
target: elm1,
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (ed) => {
|
||||
assert.strictEqual(ed.targetElm, elm2);
|
||||
done();
|
||||
@ -83,6 +86,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
it('selector on non existing targets', () => {
|
||||
return EditorManager.init({
|
||||
selector: '#non-existing-id',
|
||||
license_key: 'gpl',
|
||||
}).then((result) => {
|
||||
assert.lengthOf(result, 0, 'Should be a result that is zero length');
|
||||
});
|
||||
@ -98,6 +102,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
EditorManager.init({
|
||||
selector: '.elm-even',
|
||||
target: elm1,
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (ed) => {
|
||||
assert.notStrictEqual(ed.targetElm, elm1, 'target option ignored');
|
||||
assert.notInclude(targets, ed.targetElm);
|
||||
@ -117,6 +122,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
base_url: '/compiled/fake/url',
|
||||
suffix: '.min',
|
||||
selector: '#elm-1',
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (ed) => {
|
||||
assert.equal(EditorManager.suffix, '.min', 'Should have set suffix on EditorManager');
|
||||
assert.equal(ed.suffix, '.min', 'Should have set suffix on editor');
|
||||
@ -135,6 +141,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
viewBlock.update('<div class="tinymce-editor"><p>a</p></div>');
|
||||
await EditorManager.init({
|
||||
selector: '.tinymce-editor',
|
||||
license_key: 'gpl',
|
||||
inline: true,
|
||||
promotion: false,
|
||||
toolbar_mode: 'wrap',
|
||||
@ -161,6 +168,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
|
||||
return EditorManager.init({
|
||||
selector: '.tinymce-editor',
|
||||
license_key: 'gpl',
|
||||
inline: true,
|
||||
promotion: false,
|
||||
toolbar_mode: 'wrap'
|
||||
@ -239,6 +247,7 @@ describe('browser.tinymce.core.init.EditorInitializationTest', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
EditorManager.init({
|
||||
selector,
|
||||
license_key: 'gpl',
|
||||
init_instance_callback: (ed) => {
|
||||
assert.equal(ed.getContent({ format: 'text' }), expectedEditorContent, 'Expect editor to have content');
|
||||
done();
|
||||
|
@ -0,0 +1,84 @@
|
||||
|
||||
import { before, context, describe, it } from '@ephox/bedrock-client';
|
||||
import { TinyHooks } from '@ephox/wrap-mcagar';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import { TinyMCE } from 'tinymce/core/api/Tinymce';
|
||||
|
||||
declare const tinymce: TinyMCE;
|
||||
|
||||
describe('browser.tinymce.core.init.LicenseKeyTest', () => {
|
||||
context('Non-GPL License Key Manager', () => {
|
||||
before(() => {
|
||||
tinymce._addLicenseKeyManager(() => {
|
||||
return {
|
||||
validate: () => Promise.resolve(false)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
context('No license key specified', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
license_key: undefined,
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
});
|
||||
|
||||
it('TINY-12058: editor.licenseKeyManager should be defined', () => {
|
||||
const editor = hook.editor();
|
||||
assert.isObject(editor.licenseKeyManager);
|
||||
assert.isFunction(editor.licenseKeyManager.validate);
|
||||
});
|
||||
|
||||
it('TINY-12058: validate should return false by default', async () => {
|
||||
const editor = hook.editor();
|
||||
const result = await editor.licenseKeyManager.validate({});
|
||||
assert.isFalse(result);
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-GPL license key specified', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
license_key: 'foo'
|
||||
}, []);
|
||||
|
||||
it('TINY-12058: editor.licenseKeyManager should be defined', () => {
|
||||
const editor = hook.editor();
|
||||
assert.isObject(editor.licenseKeyManager);
|
||||
assert.isFunction(editor.licenseKeyManager.validate);
|
||||
});
|
||||
|
||||
it('TINY-12058: validate should return true by default', async () => {
|
||||
const editor = hook.editor();
|
||||
const result = await editor.licenseKeyManager.validate({});
|
||||
assert.isFalse(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('GPL License Key Manager', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
license_key: 'gpl'
|
||||
});
|
||||
|
||||
it('TINY-12058: editor.licenseKeyManager should be defined', () => {
|
||||
const editor = hook.editor();
|
||||
assert.isObject(editor.licenseKeyManager);
|
||||
assert.isFunction(editor.licenseKeyManager.validate);
|
||||
});
|
||||
|
||||
it('TINY-12058: validate should return true by default', async () => {
|
||||
const editor = hook.editor();
|
||||
const result = await editor.licenseKeyManager.validate({});
|
||||
assert.isTrue(result);
|
||||
});
|
||||
|
||||
it('TINY-12058: validate should return false when given any plugin', async () => {
|
||||
const editor = hook.editor();
|
||||
const result = await editor.licenseKeyManager.validate({ plugin: 'foo' });
|
||||
assert.isFalse(result);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,144 +0,0 @@
|
||||
|
||||
import { after, afterEach, before, context, describe, it } from '@ephox/bedrock-client';
|
||||
import { Arr, Fun, Global } from '@ephox/katamari';
|
||||
import { TinyHooks } from '@ephox/mcagar';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import * as LicenseKeyValidation from 'tinymce/core/init/LicenseKeyValidation';
|
||||
|
||||
describe('browser.tinymce.core.init.LicenseKeyValidationTest', () => {
|
||||
let oldWarn: typeof console.warn;
|
||||
let messages: string[] = [];
|
||||
const expectedLogMessage = `TinyMCE is running in evaluation mode. Provide a valid license key or add license_key: 'gpl' to the init config to agree to the open source license terms. Read more at https://www.tiny.cloud/license-key/`;
|
||||
const invalidGeneratedKeyToShort = Arr.range(63, Fun.constant('x')).join('');
|
||||
const invalidGeneratedKeyToLong = Arr.range(512, Fun.constant('x')).join('');
|
||||
const validGeneratedKey = Arr.range(67, Fun.constant('x')).join('');
|
||||
|
||||
const beforeHandler = () => {
|
||||
messages = [];
|
||||
oldWarn = Global.console.warn;
|
||||
Global.console.warn = (...args: any[]) => {
|
||||
messages.push(Arr.map(args, (arg) => String(arg)).join(' '));
|
||||
};
|
||||
};
|
||||
|
||||
const afterHandler = () => {
|
||||
Global.console.warn = oldWarn;
|
||||
};
|
||||
|
||||
context('LicenseKeyValidator API', () => {
|
||||
before(beforeHandler);
|
||||
after(afterHandler);
|
||||
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
hook.editor().options.unset('license_key');
|
||||
});
|
||||
|
||||
it('validateLicenseKey GPL', () => {
|
||||
assert.equal(LicenseKeyValidation.validateLicenseKey('GPL'), 'VALID');
|
||||
assert.equal(LicenseKeyValidation.validateLicenseKey('gPl'), 'VALID');
|
||||
assert.equal(LicenseKeyValidation.validateLicenseKey('gpl'), 'VALID');
|
||||
});
|
||||
|
||||
it('validateLicenseKey generated', () => {
|
||||
assert.equal(LicenseKeyValidation.validateLicenseKey(invalidGeneratedKeyToShort), 'INVALID', 'To short');
|
||||
assert.equal(LicenseKeyValidation.validateLicenseKey(invalidGeneratedKeyToLong), 'INVALID', 'To to long');
|
||||
assert.equal(LicenseKeyValidation.validateLicenseKey(validGeneratedKey), 'VALID', 'Is valid');
|
||||
});
|
||||
|
||||
it('validateEditorLicenseKey no license key set', () => {
|
||||
messages = [];
|
||||
LicenseKeyValidation.validateEditorLicenseKey(hook.editor());
|
||||
assert.deepEqual(messages, [ expectedLogMessage ], 'Should produce a message since license_key is missing');
|
||||
});
|
||||
|
||||
it('validateEditorLicenseKey GPL license key set', () => {
|
||||
const editor = hook.editor();
|
||||
|
||||
messages = [];
|
||||
editor.options.set('license_key', 'gpl');
|
||||
LicenseKeyValidation.validateEditorLicenseKey(hook.editor());
|
||||
assert.deepEqual(messages, [ ], 'Should not produce a message since GPL is valid');
|
||||
});
|
||||
|
||||
it('validateEditorLicenseKey generated valid license key set', () => {
|
||||
const editor = hook.editor();
|
||||
|
||||
messages = [];
|
||||
editor.options.set('license_key', validGeneratedKey);
|
||||
LicenseKeyValidation.validateEditorLicenseKey(hook.editor());
|
||||
assert.deepEqual(messages, [ ], 'Should not produce a message since generated key is valid');
|
||||
});
|
||||
|
||||
it('validateEditorLicenseKey api_key set but no license_key', () => {
|
||||
const editor = hook.editor();
|
||||
|
||||
messages = [];
|
||||
editor.options.set('api_key', 'some-api-key');
|
||||
LicenseKeyValidation.validateEditorLicenseKey(hook.editor());
|
||||
assert.deepEqual(messages, [ ], 'Should not produce a message since an api_key was provided');
|
||||
editor.options.unset('api_key');
|
||||
});
|
||||
});
|
||||
|
||||
context('No license key specified', () => {
|
||||
before(beforeHandler);
|
||||
after(afterHandler);
|
||||
|
||||
TinyHooks.bddSetupLight<Editor>({
|
||||
license_key: undefined,
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
});
|
||||
|
||||
it('Should have warned while initializing the editor', () => {
|
||||
assert.deepEqual(messages, [ expectedLogMessage ]);
|
||||
});
|
||||
});
|
||||
|
||||
context('GPL license key specified', () => {
|
||||
before(beforeHandler);
|
||||
after(afterHandler);
|
||||
|
||||
TinyHooks.bddSetupLight<Editor>({
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
license_key: 'gpl'
|
||||
});
|
||||
|
||||
it('Should not have any warning messages since gpl was provided', () => {
|
||||
assert.deepEqual(messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
context('Invalid license key specified', () => {
|
||||
before(beforeHandler);
|
||||
after(afterHandler);
|
||||
|
||||
TinyHooks.bddSetupLight<Editor>({
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
license_key: 'foo'
|
||||
}, []);
|
||||
|
||||
it('Should have warned while initializing the editor since the key is to short', () => {
|
||||
assert.deepEqual(messages, [ expectedLogMessage ]);
|
||||
});
|
||||
});
|
||||
|
||||
context('api_key specified', () => {
|
||||
before(beforeHandler);
|
||||
after(afterHandler);
|
||||
|
||||
TinyHooks.bddSetupLight<Editor>({
|
||||
base_url: '/project/tinymce/js/tinymce',
|
||||
api_key: 'some-api-key'
|
||||
});
|
||||
|
||||
it('Should not have any warning messages since an api_key was provided', () => {
|
||||
assert.deepEqual(messages, []);
|
||||
});
|
||||
});
|
||||
});
|
12
modules/tinymce/src/core/test/ts/module/test/UuidUtils.ts
Normal file
12
modules/tinymce/src/core/test/ts/module/test/UuidUtils.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { assert } from 'chai';
|
||||
|
||||
const assertIsUuid = (uuid: string): void => {
|
||||
// From https://github.com/uuidjs/uuid/blob/main/src/regex.js
|
||||
const v4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
assert.isString(uuid);
|
||||
assert.match(uuid, v4Regex);
|
||||
};
|
||||
|
||||
export {
|
||||
assertIsUuid
|
||||
};
|
Reference in New Issue
Block a user