// TYPESCRIPT_PATH may point to the TypeScript bundled with Cocos Creator. const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const ts = require(process.env.TYPESCRIPT_PATH || 'typescript'); const root = path.resolve(__dirname, '..'); const configFile = 'assets/Script/module/Config/GameConfig.ts'; const homeFile = 'assets/Script/JiaZai.ts'; function loadMembers(file, names, globals = {}) { const source = ts.createSourceFile(file, fs.readFileSync(path.join(root, file), 'utf8'), ts.ScriptTarget.Latest, true); const declaration = source.statements.find(ts.isClassDeclaration); const members = names.map(name => { const member = declaration.members.find(node => node.name && node.name.getText(source) === name); assert.ok(member, `${file}: ${name}`); return member.getText(source); }); const code = ts.transpileModule(`class Subject { ${members.join('\n')} }\nSubject;`, { compilerOptions: { target: ts.ScriptTarget.ES2017 }, }).outputText; return vm.runInNewContext(code, globals); } function config(variant = 3, overrides = {}) { const subject = loadMembers(configFile, [ 'isNewPlayerB', 'isWinStreakUnlocked', 'isRainbowWinStreak', 'isOldRainbowWinStreak', 'usesRainbowWinStreakUI', 'isRainbowRewardActive', 'isOldRainbowRewardActive', ]); subject.forceRainbowWinStreakB = false; subject.forceOldRainbowWinStreakC = false; subject.GM_INFO = { abTests: { layer_two: variant }, level: 100, otherLevel: 0, winStreak: 10, ...overrides }; subject.isGameplayEntryPreparing = () => false; return subject; } test('A, B and C keep separate gameplay flags while B and C share presentation', () => { for (const [variant, expected] of [[0, [false, false, false]], [1, [false, false, false]], [2, [true, false, true]], [3, [false, true, true]], [4, [false, false, false]]]) { const c = config(variant); assert.deepEqual([c.isRainbowWinStreak(), c.isOldRainbowWinStreak(), c.usesRainbowWinStreakUI()], expected); } for (const info of [null, {}, { abTests: null }]) { const c = config(); c.GM_INFO = info; assert.equal(c.isOldRainbowWinStreak(), false); assert.equal(c.isOldRainbowRewardActive(), false); assert.equal(c.usesRainbowWinStreakUI(), false); } }); test('C uses the existing win-streak, mainline and newcomer unlock requirements', () => { for (const newPlayerB of [false, true]) for (const level of [0, 49, 50, 100]) { for (const winStreak of [0, 9, 10, 11]) for (const otherLevel of [0, 25]) { const info = { level, winStreak, otherLevel, abTestAssignments: newPlayerB ? { layer_1: { name: 'new1_50', group: 'B', enabled: true } } : {} }; const old = config(3, info), current = config(2, info); const eligible = (!newPlayerB || level >= 50) && winStreak >= 10 && otherLevel === 0; assert.equal(old.isOldRainbowRewardActive(), eligible); assert.equal(current.isRainbowRewardActive(), eligible); assert.equal(old.isRainbowRewardActive(), false); assert.equal(current.isOldRainbowRewardActive(), false); } } assert.equal(config(1).isOldRainbowRewardActive(), false); }); test('forced B overrides C without enabling both rewards', () => { const c = config(3); c.forceRainbowWinStreakB = true; assert.equal(c.isRainbowWinStreak(), true); assert.equal(c.isRainbowRewardActive(), true); assert.equal(c.isOldRainbowWinStreak(), false); assert.equal(c.isOldRainbowRewardActive(), false); assert.equal(c.usesRainbowWinStreakUI(), true); }); test('rainbow test modes default off; explicitly enabling C overrides groups and unlocks early levels', () => { const Defaults = loadMembers(configFile, ['forceOldRainbowWinStreakC', 'forceRainbowWinStreakB']); assert.equal(Defaults.forceOldRainbowWinStreakC, false); assert.equal(Defaults.forceRainbowWinStreakB, false); for (const group of [1, 2, 3]) { const c = config(group, { level: 0, abTestAssignments: { layer_1: { name: 'new1_50', group: 'B', enabled: true } } }); c.forceOldRainbowWinStreakC = true; c.forceRainbowWinStreakB = true; assert.equal(c.isRainbowWinStreak(), false); assert.equal(c.isOldRainbowRewardActive(), true); const unlock = loadMembers(configFile, ['getFeatureUnlockLevel']).getFeatureUnlockLevel; assert.equal(unlock.call(c, 'winStreak'), 0); assert.equal(c.GM_INFO.abTests.layer_two, group); c.forceOldRainbowWinStreakC = Defaults.forceOldRainbowWinStreakC; c.forceRainbowWinStreakB = Defaults.forceRainbowWinStreakB; assert.equal(c.isOldRainbowWinStreak(), group === 3); assert.equal(c.isRainbowWinStreak(), group === 2); assert.equal(c.isWinStreakUnlocked(), false); assert.equal(unlock.call(c, 'winStreak'), 50); } }); test('home preloading and popup opening select the same prefab for each variant', async () => { for (const variant of [1, 2, 3]) { const c = config(variant), calls = [], queued = [], cache = {}; const prefabPath = variant === 1 ? 'prefab/winStreak' : 'prefab/winStreak2'; const cacheKey = variant === 1 ? 'cachedWinStreakPrefab' : 'cachedWinStreak2Prefab'; const prefab = {}; let initialized = 0, instantiated; const cc = { Prefab: {}, fx: { GameConfig: c, GameTool: { shushu_Track() {} } }, instantiate(value) { instantiated = value; return { getComponent: () => ({ init() { initialized++; } }) }; } }; const Home = loadMembers(homeFile, ['preloadHomePopupPrefabs', 'openWinStreak'], { cc, JiaZai: cache }); const home = new Home(); home.queueHomeAutomaticTask = (name, run) => queued.push({ name, run }); home.loadHomePopupPrefab = (...args) => { calls.push(args); return Promise.resolve(); }; home.isHomeRuntimeAlive = () => true; home.preloadHomePopupPrefabs(); const streakTasks = queued.filter(task => task.name.startsWith('prefab/winStreak')); assert.equal(streakTasks.length, 1); streakTasks[0].run(); await Promise.resolve(); assert.deepEqual(calls, [['action_bundle', prefabPath, cacheKey, -10]]); cache[cacheKey] = prefab; home.vibrateButtonClick = () => {}; home.node = { addChild() {} }; home.openWinStreak({}, 'winStreak'); assert.equal(instantiated, prefab); assert.equal(initialized, 1); } }); test('settings use the rainbow panel for B and C and keep the A panel', () => { for (const variant of [1, 2, 3]) for (const hasRainbowPanel of [false, true]) { const Settings = loadMembers('assets/Script/setUi.ts', ['onLoad'], { cc: { fx: { GameConfig: config(variant) } } }); const settings = new Settings(), original = {}, rainbow = hasRainbowPanel ? {} : null; Object.assign(settings, { win: original, win2: rainbow, music: { children: [{}, {}] }, effect: { children: [{}, {}] }, vibrate: null }); settings.onLoad(); assert.equal(settings.win, hasRainbowPanel && variant !== 1 ? rainbow : original); } });