Files
gongxue-base/scripts/taro-components-h5-runtime-patch-test.js
2026-07-12 19:26:57 +08:00

140 lines
5.8 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
enforceTaroH5RuntimePatches,
replaceExactlyOnce,
taroButtonLoadingPatch,
taroH5RuntimePatchDefinition,
taroInputWatcherPatch,
} from './taro-components-h5-runtime-patch.js';
const repoRoot = process.cwd();
function writeJson(filePath, value) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
function installedPristineSource(patch) {
const target = path.join(repoRoot, 'node_modules', '@tarojs', 'components', patch.targetRelativePath);
const installed = fs.readFileSync(target, 'utf8');
const pristine = installed.includes(patch.after) ? installed.replace(patch.after, patch.before) : installed;
return pristine;
}
const pristineSources = new Map([
[taroInputWatcherPatch.id, installedPristineSource(taroInputWatcherPatch)],
[taroButtonLoadingPatch.id, installedPristineSource(taroButtonLoadingPatch)],
]);
function createFixture(options = {}) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-taro-h5-runtime-patch-'));
const taroManifest = {
name: '@tiku-saas/taro',
version: '0.1.0',
scripts: { postinstall: taroH5RuntimePatchDefinition.postinstallCommand },
devDependencies: {
'@tarojs/components': options.declaredVersion || taroH5RuntimePatchDefinition.packageVersion,
},
};
writeJson(path.join(root, 'apps', 'taro', 'package.json'), taroManifest);
writeJson(path.join(root, 'package-lock.json'), {
lockfileVersion: 3,
packages: {
'apps/taro': {
hasInstallScript: true,
devDependencies: {
'@tarojs/components': options.lockWorkspaceVersion || taroH5RuntimePatchDefinition.packageVersion,
},
},
'node_modules/@tarojs/components': {
version: options.lockVersion || taroH5RuntimePatchDefinition.packageVersion,
integrity: options.integrity || taroH5RuntimePatchDefinition.lockIntegrity,
},
},
});
writeJson(path.join(root, 'node_modules', '@tarojs', 'components', 'package.json'), {
name: '@tarojs/components',
version: options.installedVersion || taroH5RuntimePatchDefinition.packageVersion,
});
const targets = new Map();
for (const patch of taroH5RuntimePatchDefinition.patches) {
const target = path.join(root, 'node_modules', '@tarojs', 'components', patch.targetRelativePath);
targets.set(patch.id, target);
if (options.missingTarget !== patch.id) {
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(
target,
options.sources?.[patch.id] ?? pristineSources.get(patch.id),
'utf8',
);
}
}
return { root, targets };
}
function targetContents(fixture) {
return Object.fromEntries([...fixture.targets].map(([id, target]) => [
id,
fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null,
]));
}
const workingState = enforceTaroH5RuntimePatches({ root: repoRoot, mode: 'check' });
assert.equal(workingState.status, 'pass');
assert.equal(workingState.patches.inputWatcher.installedSha256, taroInputWatcherPatch.patchedSha256);
assert.equal(workingState.patches.buttonLoading.installedSha256, taroButtonLoadingPatch.patchedSha256);
const applied = createFixture();
try {
const first = enforceTaroH5RuntimePatches({ root: applied.root, mode: 'apply' });
assert.equal(first.patches.inputWatcher.state, 'patched-now');
assert.equal(first.patches.buttonLoading.state, 'patched-now');
const second = enforceTaroH5RuntimePatches({ root: applied.root, mode: 'apply' });
assert.equal(second.patches.inputWatcher.state, 'patched');
assert.equal(second.patches.buttonLoading.state, 'patched');
assert.equal(enforceTaroH5RuntimePatches({ root: applied.root, mode: 'check' }).status, 'pass');
} finally {
fs.rmSync(applied.root, { recursive: true, force: true });
}
const pristineCheck = createFixture();
try {
const before = targetContents(pristineCheck);
assert.throws(
() => enforceTaroH5RuntimePatches({ root: pristineCheck.root, mode: 'check' }),
/patch is not applied/,
);
assert.deepEqual(targetContents(pristineCheck), before, '--check must never modify a pristine install');
} finally {
fs.rmSync(pristineCheck.root, { recursive: true, force: true });
}
for (const [name, options, pattern] of [
['declared version', { declaredVersion: '4.2.1' }, /must pin/],
['lock integrity', { integrity: 'sha512-unreviewed' }, /unexpected.*integrity/],
['installed version', { installedVersion: '4.2.1-beta.2' }, /does not match/],
['missing Input target', { missingTarget: taroInputWatcherPatch.id }, /target is missing/],
['missing Button target', { missingTarget: taroButtonLoadingPatch.id }, /target is missing/],
['unknown Input content', { sources: { [taroInputWatcherPatch.id]: `${pristineSources.get(taroInputWatcherPatch.id)}\n// tampered\n` } }, /unreviewed.*hash/],
['unknown Button content', { sources: { [taroButtonLoadingPatch.id]: `${pristineSources.get(taroButtonLoadingPatch.id)}\n// tampered\n` } }, /unreviewed.*hash/],
]) {
const fixture = createFixture(options);
try {
const before = targetContents(fixture);
assert.throws(() => enforceTaroH5RuntimePatches({ root: fixture.root, mode: 'apply' }), pattern, name);
assert.deepEqual(targetContents(fixture), before, `${name} failure must not modify either target`);
} finally {
fs.rmSync(fixture.root, { recursive: true, force: true });
}
}
assert.throws(() => replaceExactlyOnce('safe', 'unsafe', 'guarded'), /found 0/);
assert.throws(() => replaceExactlyOnce('unsafe unsafe', 'unsafe', 'guarded'), /found 2/);
assert.equal(replaceExactlyOnce('before unsafe after', 'unsafe', 'guarded'), 'before guarded after');
console.log('[PASS] Taro H5 runtime patch contract');