import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { test } from 'node:test'; import vm from 'node:vm'; import ts from 'typescript'; import * as config from '../miniprogram/papersPages/paperSheet/paperSheet.config.ts'; import { PAPER_SHEET_WORKSHEET_DEFINITIONS } from '../miniprogram/config/worksheets/papers.ts'; import * as debugPublish from '../miniprogram/utils/debugPublish.ts'; import { ApiRequestError } from '../miniprogram/services/api-transport.ts'; const route = 'papersPages/paperSheet/paperSheet'; const deferred = () => { let resolve; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve }; }; /** 使用真实页面和公共分享/导出方法,仅隔离微信、网络、绘图与埋点。 */ function createPage({ getWorksheet, favorite = async () => true, check = async () => ({}), } = {}) { const toasts = [], downloads = [], favorites = []; const wx = { getAccountInfoSync: () => ({ miniProgram: { envVersion: 'release' } }), setNavigationBarTitle() {}, showToast: (options) => toasts.push(options.title), }; const load = (file, modules) => { const exports = {}; const source = readFileSync(new URL(file, import.meta.url), 'utf8'); vm.runInNewContext( ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, }, }).outputText, { exports, wx, console, Error, require(id) { assert.ok(id in modules, id); return modules[id]; }, }, ); return exports; }; const defaultShareConfig = { title: '分享', path: '/pages/index/index', imageUrl: '', }; const mixin = load('../miniprogram/base/pageMixin.ts', { '../services/member-api': {}, '../services/api-request': {}, '../constants/colors': {}, '../core/draw/baseDraw': {}, '../utils/downloadPrint': { downloadPrint: async (_canvas, options) => downloads.push(options), }, '../utils/shareGuide': { shouldUnlockWithAd: () => false, shouldShowShareGuide: () => false, }, '../utils/tracker': { default: new Proxy({}, { get: () => () => {} }) }, '../config/config': { defaultShareConfig }, '../utils/debugPublish': { ...debugPublish, isDebugPublishEnabled: () => wx.getAccountInfoSync().miniProgram.envVersion === 'develop', }, }); let options; load('../miniprogram/papersPages/paperSheet/paperSheet.ts', { './draw/paperDrawService': {}, './paperSheet.config': config, '../../base/pageMixin': { ...mixin, createPage(value, settings) { options = mixin.applyPageMixin(value, settings); }, }, '../../config/config': { defaultShareConfig }, '../../services/api-request': { ApiRequestError }, '../../services/content-api': { getWorksheet: getWorksheet || (async (id) => ({ id, rendering: { rendererKey: 'paper-sheet', paperType: config.resolvePaperEntry({ id }).paperType, }, })), }, '../../utils/favorites': { addFavorite: async (id) => { favorites.push(['add', id]); return favorite(id); }, removeFavorite: async (id) => { favorites.push(['remove', id]); return favorite(id); }, batchCheckFavorited: check, }, }); const page = { ...options, route, data: structuredClone(options.data), setData(value, callback) { Object.assign(this.data, value); callback?.(); }, drawService: { async draw() {} }, canvas: {}, }; return { page, toasts, downloads, favorites, wx }; } test('十种独立条目复用绘制器,旧入口/type 分享兼容,独立 ID 不受额外 type 干扰', () => { assert.equal(PAPER_SHEET_WORKSHEET_DEFINITIONS.length, 11); for (const paper of config.PAPER_TYPE_OPTIONS) { const id = config.getWorksheetIdForPaperType(paper.id); const { entry, paperType } = config.resolvePaperEntry({ id, type: 'unknown', }); assert.equal(paperType, paper.id); assert.equal(entry.rendererKey, 'paper-sheet'); assert.equal( config.getPublishMetaByMode(id).path, `/${route}?id=${id}`, ); } assert.equal(config.resolvePaperEntry({}).entry.id, 'paper-sheet'); assert.equal( config.resolvePaperEntry({ id: 'paper-sheet', type: 'mi' }).paperType, 'mi', ); assert.equal( config.resolvePaperEntry({ id: 'paper-sheet', mode: 'vertical' }) .paperType, 'vertical', ); assert.throws(() => config.resolvePaperEntry({ id: 'paper-unknown' })); }); test('切换后页面身份、收藏、分享、发布和实际导出统计均属于当前条目', async () => { const { page, downloads, favorites } = createPage(); await page.onLoad({ id: 'paper-mi' }); assert.equal(page.data.pageTitle, '米字格'); assert.equal(page.data.functionId, 'paper-mi'); assert.equal(page.data.selectedPaperType, 'mi'); assert.equal(page.onShareAppMessage().path, `/${route}?id=paper-mi`); await page.onPreviewFavorite(); assert.deepEqual(favorites, [['add', 'paper-mi']]); await page.exportToPrint(); assert.equal(downloads[0].worksheetId, 'paper-mi'); page.setData({ debugPublishVisible: true, debugPublishMeta: page.getPublishMeta(), }); await page.onSelectPaperType({ currentTarget: { dataset: { id: 'tian' } }, }); assert.equal(page.data.debugPublishVisible, false); assert.equal(page.data.debugPublishMeta, null); assert.equal(page.data.functionId, 'paper-tian'); assert.equal(page.data.isPreviewFavorite, false); assert.equal(page.getPublishMeta().id, 'paper-tian'); assert.equal(page.onShareTimeline().query, 'id=paper-tian'); await page.onSelectPaperType({ currentTarget: { dataset: { id: 'mi' } } }); assert.equal(page.data.isPreviewFavorite, true); await page.onPreviewFavorite(); assert.deepEqual(favorites[1], ['remove', 'paper-mi']); await page.onLoad({ id: 'paper-sheet', type: 'vertical' }); assert.equal( page.onShareAppMessage().path, `/${route}?id=paper-sheet&mode=vertical`, ); }); test('快速切换丢弃旧响应,加载期间禁止导出,未知条目/下架/配置错误不生成其他纸张', async () => { const old = deferred(); const { page, downloads, toasts } = createPage({ getWorksheet: (id) => id === 'paper-mi' ? old.promise : Promise.resolve({ rendering: { rendererKey: 'paper-sheet', paperType: 'tian', }, }), }); const first = page.onLoad({ id: 'paper-mi' }); await page.exportToPrint(); assert.equal(downloads.length, 0); await page.onSelectPaperType({ currentTarget: { dataset: { id: 'tian' } }, }); old.resolve({ rendering: { rendererKey: 'paper-sheet', paperType: 'mi' } }); await first; assert.equal(page.data.worksheetId, 'paper-tian'); await page.openEntry({ id: 'paper-unknown' }); assert.equal(page.data.hasContent, false); assert.ok(toasts.length); for (const getWorksheet of [ async () => { throw new ApiRequestError('已下架', 404); }, async () => ({ rendering: { rendererKey: 'paper-sheet', paperType: 'tian' }, }), ]) { const blocked = createPage({ getWorksheet }).page; await blocked.onLoad({ id: 'paper-mi' }); assert.equal(blocked.data.hasContent, false); } }); test('收藏失败不报成功,慢速查询不能覆盖新收藏,切换时收藏响应不写错条目', async () => { const pending = deferred(); const { page, toasts } = createPage({ favorite: async () => false }); await page.onLoad({ id: 'paper-mi' }); await page.onPreviewFavorite(); assert.equal(page.data.isPreviewFavorite, false); assert.ok(toasts.some((title) => title.includes('失败'))); const active = createPage({ check: () => pending.promise }).page; await active.onLoad({ id: 'paper-mi' }); await active.onPreviewFavorite(); pending.resolve({ 'paper-mi': false }); await new Promise((resolve) => setImmediate(resolve)); assert.equal(active.data.isPreviewFavorite, true); const write = deferred(); const switching = createPage({ favorite: () => write.promise }).page; await switching.onLoad({ id: 'paper-mi' }); const saving = switching.onPreviewFavorite(); await switching.openEntry({ id: 'paper-tian' }); write.resolve(true); await saving; assert.equal(switching.data.isPreviewFavorite, false); assert.equal(switching._favoritedMap['paper-mi'], true); }); test('开发版离线预览十种已注册纸张,切换不请求公开详情,未知 ID 仍不可用', async () => { let requests = 0; const { page, wx, toasts } = createPage({ getWorksheet: async () => { requests++; throw new Error('开发预览不应请求公开详情'); }, }); wx.getAccountInfoSync = () => ({ miniProgram: { envVersion: 'develop' } }); await page.onLoad({ id: 'paper-sheet' }); for (const paper of config.PAPER_TYPE_OPTIONS) { await page.onSelectPaperType({ currentTarget: { dataset: { id: paper.id } }, }); assert.equal(page.data.hasContent, true); assert.equal(page.data.selectedPaperType, paper.id); assert.equal( page.getPublishMeta().id, config.getWorksheetIdForPaperType(paper.id), ); } assert.equal(requests, 0); assert.equal(toasts.length, 0); await page.openEntry({ id: 'paper-unknown' }); assert.equal(page.data.hasContent, false); assert.equal(requests, 0); assert.equal(toasts.length, 1); }); test('体验版与正式版均校验公开详情,404 和网络故障不能降级成本地预览', async () => { for (const version of ['trial', 'release']) { for (const failure of [ new ApiRequestError('未发布', 404), new ApiRequestError('网络故障', 0), ]) { let requests = 0; const { page, wx } = createPage({ getWorksheet: async () => { requests++; throw failure; }, }); wx.getAccountInfoSync = () => ({ miniProgram: { envVersion: version }, }); await page.onLoad({ id: 'paper-square' }); assert.equal(requests, 1); assert.equal(page.data.hasContent, false); assert.equal(page.data.isWorksheetReady, false); } } });