Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added more plugin tests #1969

Merged
merged 18 commits into from Oct 5, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
711 changes: 280 additions & 431 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -57,7 +57,7 @@
"gzip-size": "^5.1.1",
"htmlparser2": "^4.0.0",
"http-server": "^0.12.3",
"jsdom": "^13.0.0",
"jsdom": "^16.7.0",
"mocha": "^6.2.0",
"node-fetch": "^2.6.0",
"npm-run-all": "^4.1.5",
Expand Down
58 changes: 58 additions & 0 deletions tests/helper/prism-dom-util.js
@@ -0,0 +1,58 @@
const { assert } = require('chai');
const PrismLoader = require('./prism-loader');

/**
* @typedef {import("./prism-loader").PrismDOM} PrismDOM
* @typedef {import("./prism-loader").PrismWindow} PrismWindow
*/

module.exports = {
/**
* @param {PrismWindow} window
*/
createUtil(window) {
const { Prism, document } = window;

const util = {
assert: {
highlight({ language = 'none', code, expected }) {
assert.strictEqual(Prism.highlight(code, Prism.languages[language], language), expected);
},
highlightElement({ language = 'none', code, expected }) {
const element = document.createElement('CODE');
element.classList.add('language-' + language);
element.textContent = code;

Prism.highlightElement(element);

assert.strictEqual(element.innerHTML, expected);
}
},
};

return util;
},

/**
* Creates a Prism DOM instance that will be automatically cleaned up after the given test suite finished.
*
* @param {ReturnType<typeof import('mocha')["suite"]>} suite
* @param {Partial<Record<"languages" | "plugins", string | string[]>>} options
*/
createScopedPrismDom(suite, options = {}) {
const dom = PrismLoader.createPrismDOM();

suite.afterAll(function () {
dom.window.close();
});

if (options.languages) {
dom.loadLanguages(options.languages);
}
if (options.plugins) {
dom.loadPlugins(options.plugins);
}

return dom;
}
};
98 changes: 90 additions & 8 deletions tests/helper/prism-loader.js
@@ -1,18 +1,34 @@
'use strict';

const fs = require('fs');
const { JSDOM } = require('jsdom');
const components = require('../../components.json');
const getLoader = require('../../dependencies');
const languagesCatalog = components.languages;
const coreChecks = require('./checks');
const { languages: languagesCatalog, plugins: pluginsCatalog } = components;


/**
* @typedef {import('../../components/prism-core')} Prism
*/

/**
* @typedef PrismLoaderContext
* @property {import('../../components/prism-core')} Prism The Prism instance.
* @property {Prism} Prism The Prism instance.
* @property {Set<string>} loaded A set of loaded components.
*/

/**
* @typedef {import("jsdom").DOMWindow & { Prism: Prism }} PrismWindow
*
* @typedef PrismDOM
* @property {JSDOM} dom
* @property {PrismWindow} window
* @property {Document} document
* @property {Prism} Prism
* @property {(languages: string | string[]) => void} loadLanguages
* @property {(plugins: string | string[]) => void} loadPlugins
*/

/** @type {Map<string, string>} */
const fileSourceCache = new Map();
/** @type {() => any} */
Expand All @@ -39,6 +55,54 @@ module.exports = {
return context.Prism;
},

/**
* Creates a new JavaScript DOM instance with Prism being loaded.
*
* @returns {PrismDOM}
*/
createPrismDOM() {
const dom = new JSDOM(``, {
runScripts: 'outside-only'
});
const window = dom.window;

window.self = window; // set self for plugins
window.eval(this.loadComponentSource('core'));

/** The set of loaded languages and plugins */
const loaded = new Set();

/**
* Loads the given languages or plugins.
*
* @param {string | string[]} languagesOrPlugins
*/
const load = (languagesOrPlugins) => {
getLoader(components, toArray(languagesOrPlugins), [...loaded]).load(id => {
let source;
if (languagesCatalog[id]) {
source = this.loadComponentSource(id);
} else if (pluginsCatalog[id]) {
source = this.loadPluginSource(id);
} else {
throw new Error(`Language or plugin '${id}' not found.`);
}

window.eval(source);
loaded.add(id);
});
};

return {
dom,
window: /** @type {PrismWindow} */ (window),
document: window.document,
Prism: window.Prism,
loadLanguages: load,
loadPlugins: load,
};
},

/**
* Loads the given languages and appends the config to the given Prism object.
*
Expand All @@ -48,11 +112,7 @@ module.exports = {
* @returns {PrismLoaderContext}
*/
loadLanguages(languages, context) {
if (typeof languages === 'string') {
languages = [languages];
}

getLoader(components, languages, [...context.loaded]).load(id => {
getLoader(components, toArray(languages), [...context.loaded]).load(id => {
if (!languagesCatalog[id]) {
throw new Error(`Language '${id}' not found.`);
}
Expand Down Expand Up @@ -112,6 +172,17 @@ module.exports = {
return this.loadFileSource(__dirname + '/../../components/prism-' + name + '.js');
},

/**
* Loads the given plugin's file source as string
*
* @private
* @param {string} name
* @returns {string}
*/
loadPluginSource(name) {
return this.loadFileSource(`${__dirname}/../../plugins/${name}/prism-${name}.js`);
},

/**
* Loads the given file source as string
*
Expand All @@ -127,3 +198,14 @@ module.exports = {
return content;
}
};

/**
* Wraps the given value in an array if it's not an array already.
*
* @param {T[] | T} value
* @returns {T[]}
* @template T
*/
function toArray(value) {
return Array.isArray(value) ? value : [value];
}
71 changes: 71 additions & 0 deletions tests/plugins/copy-to-clipboard/basic-functionality.js
@@ -0,0 +1,71 @@
const { assert } = require('chai');
const { createScopedPrismDom } = require('../../helper/prism-dom-util');


class DummyClipboard {
constructor() {
this.text = '';
}
async readText() {
return this.text;
}
/** @param {string} data */
writeText(data) {
this.text = data;
return Promise.resolve();
}
}

describe('Copy to Clipboard', function () {
const { Prism, document, window } = createScopedPrismDom(this, {
languages: 'javascript',
plugins: 'copy-to-clipboard',
});


it('should work', function () {
const clipboard = new DummyClipboard();
window.navigator.clipboard = clipboard;

document.body.innerHTML = `<pre class="language-none"><code>foo</code></pre>`;
Prism.highlightAll();

const button = document.querySelector('button');
assert.notStrictEqual(button, null);

button.click();

assert.strictEqual(clipboard.text, 'foo');
});

it('should copy the current text even after the code block changes its text', function () {
const clipboard = new DummyClipboard();
window.navigator.clipboard = clipboard;

document.body.innerHTML = `<pre class="language-none"><code>foo</code></pre>`;
Prism.highlightAll();

const button = document.querySelector('button');
assert.notStrictEqual(button, null);

button.click();

assert.strictEqual(clipboard.text, 'foo');

// change text
document.querySelector('code').textContent = 'bar';
// and click
button.click();

assert.strictEqual(clipboard.text, 'bar');

// change text
document.querySelector('code').textContent = 'baz';
Prism.highlightAll();
// and click
button.click();

assert.strictEqual(clipboard.text, 'baz');
});

});
69 changes: 69 additions & 0 deletions tests/plugins/custom-class/basic-functionality.js
@@ -0,0 +1,69 @@
const { createUtil, createScopedPrismDom } = require('../../helper/prism-dom-util');


describe('Custom class', function () {
const { Prism, window } = createScopedPrismDom(this, {
languages: 'javascript',
plugins: 'custom-class'
});
const util = createUtil(window);


it('should set prefix', function () {
Prism.plugins.customClass.prefix('prism-');

util.assert.highlight({
language: 'javascript',
code: `var a = true;`,
expected: `<span class="prism-token prism-keyword">var</span> a <span class="prism-token prism-operator">=</span> <span class="prism-token prism-boolean">true</span><span class="prism-token prism-punctuation">;</span>`
});
});

it('should reset prefix', function () {
Prism.plugins.customClass.prefix('');

util.assert.highlight({
language: 'javascript',
code: `var a = true;`,
expected: `<span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>`
});
});

it('should map class names using a function', function () {
Prism.plugins.customClass.map(function (cls, language) {
return `${language}-${cls}`;
});

util.assert.highlight({
language: 'javascript',
code: `var a = true;`,
expected: `<span class="javascript-token javascript-keyword">var</span> a <span class="javascript-token javascript-operator">=</span> <span class="javascript-token javascript-boolean">true</span><span class="javascript-token javascript-punctuation">;</span>`
});
});

it('should map class names using an object', function () {
Prism.plugins.customClass.map({
boolean: 'b',
keyword: 'kw',
operator: 'op',
punctuation: 'p'
});

util.assert.highlight({
language: 'javascript',
code: `var a = true;`,
expected: `<span class="token kw">var</span> a <span class="token op">=</span> <span class="token b">true</span><span class="token p">;</span>`
});
});

it('should reset map', function () {
Prism.plugins.customClass.map({});

util.assert.highlight({
language: 'javascript',
code: `var a = true;`,
expected: `<span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>`
});
});

});
20 changes: 20 additions & 0 deletions tests/plugins/highlight-keywords/basic-functionality.js
@@ -0,0 +1,20 @@
const { createUtil, createScopedPrismDom } = require('../../helper/prism-dom-util');


describe('Highlight Keywords', function () {
const { window } = createScopedPrismDom(this, {
languages: 'javascript',
plugins: 'highlight-keywords'
});
const util = createUtil(window);


it('should highlight keywords', function () {
util.assert.highlightElement({
language: 'javascript',
code: `import * from ''; const foo;`,
expected: `<span class="token keyword keyword-import">import</span> <span class="token operator">*</span> <span class="token keyword keyword-from">from</span> <span class="token string">''</span><span class="token punctuation">;</span> <span class="token keyword keyword-const">const</span> foo<span class="token punctuation">;</span>`
});
});

});