'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const root = path.resolve(__dirname, '..', '..'); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); // Strip string literals so a "$" inside a message is not mistaken for a call. const stripStrings = (src) => src.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''"); // A jQuery stand-in exposing only what clipboard.min.js touches. Bare $ is // deliberately NOT provided, matching WordPress's noConflict mode. function makeSandbox() { const handlers = []; const created = []; const toasts = []; const body = { on(evt, childSel, ns, fn) { handlers.push({ evt, childSel, ns, fn }); return this; }, }; const documentStub = { querySelector: (sel) => (sel === '#copy-input' ? created[created.length - 1] || null : null), createElement: () => { const el = { style: {}, value: '', id: '', readOnly: '', setSelectionRange() {}, select() {}, blur() {}, }; created.push(el); return el; }, execCommand: () => true, }; const jq = (sel) => { if (sel === 'body') return body; if (sel && sel.attrs) return { attr: (n) => sel.attrs[n] }; return { on() { return this; } }; }; jq.__isElement = true; return { sandbox: { window: {}, document: documentStub, jQuery: jq, notyf: (m, t) => toasts.push({ m, t }) }, handlers, created, toasts, body }; } test('the lazily loaded clipboard lib survives WordPress noConflict', () => { const src = read('assets/js/runtime/libs/clipboard.min.js'); // No bare $ calls or $. usage anywhere in the module. const bare = stripStrings(src); assert.doesNotMatch(bare, /(^|[^.\w$])\$\(/, 'clipboard.min.js uses a bare $('); assert.doesNotMatch(bare, /\$\.\w+/, 'clipboard.min.js uses a bare $.'); // It must bind through jQuery explicitly on the element that always exists. assert.match(src, /jQuery\("body"\)\.on\("click",\s*"\[data-clipboard-text\]"/); const { sandbox, handlers, created, toasts } = makeSandbox(); sandbox.window.jQuery = sandbox.jQuery; // Evaluating with no global $ must not throw. assert.doesNotThrow(() => { new Function('window', 'document', 'jQuery', 'notyf', '"use strict";\n' + src)( sandbox.window, sandbox.document, sandbox.jQuery, sandbox.notyf ); }); assert.equal(handlers.length, 1); assert.equal(handlers[0].evt, 'click'); assert.equal(handlers[0].childSel, '[data-clipboard-text]'); // Clicking must copy the copy content and toast the copy name. const el = { attrs: { 'data-clipboard-text': 'a1b2', 'data-clipboard-tag': '提取码' }, parentNode: { appendChild() {} }, }; assert.doesNotThrow(() => handlers[0].fn.call(el, {})); assert.equal(created[created.length - 1].value, 'a1b2'); assert.deepEqual(toasts, [{ m: '提取码已复制', t: undefined }]); }); test('the readable clipboard source agrees with the loaded bundle', () => { const src = read('assets/js/runtime/libs/clipboard.js'); const bare = stripStrings(src); assert.doesNotMatch(bare, /(^|[^.\w$])\$\(/, 'clipboard.js uses a bare $('); assert.match(src, /jQuery\('body'\)\.on\('click'/); assert.match(src, /typeof notyf === 'function'/); }); test('bootstrap is unaffected because it receives jQuery as an IIFE argument', () => { const src = read('assets/js/runtime/libs/bootstrap.min.js'); assert.match(src, /!function\(\w\)\{"use strict"/); assert.match(src, /requires jQuery/); });