152 lines
8.8 KiB
JavaScript
152 lines
8.8 KiB
JavaScript
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const fs = require('node:fs');
|
||
const { loadMembers, withNewPlayerConfig, loadModule } = require('./new-player-ab-test-support.cjs');
|
||
const RainbowConfig = loadMembers('assets/Script/module/Config/GameConfig.ts', [
|
||
'isRainbowWinStreak', 'isOldRainbowWinStreak', 'usesRainbowWinStreakUI',
|
||
]);
|
||
|
||
const prefab = JSON.parse(fs.readFileSync('assets/lose/prefab/Lose.prefab', 'utf8'));
|
||
const frameData = Array.from({ length: 6 }, (_, i) => {
|
||
const meta = JSON.parse(fs.readFileSync(`assets/lose/texture/giveup${i + 1}.png.meta`, 'utf8'));
|
||
return Object.values(meta.subMetas)[0];
|
||
});
|
||
|
||
function fixture({ infinite = false, level = 50, group = 'B', streak = 10, help = 0, rainbowVariant = 1 } = {}) {
|
||
const config = withNewPlayerConfig({ forceRainbowWinStreakB: false, forceOldRainbowWinStreakC: false, GM_INFO: {
|
||
level, otherLevel: help, winStreak: streak, revive: 0,
|
||
abTests: { layer_two: rainbowVariant },
|
||
abTestAssignments: { layer_1: { name: 'new1_50', group, enabled: true } },
|
||
}, isRainbowWinStreak: RainbowConfig.isRainbowWinStreak,
|
||
isOldRainbowWinStreak: RainbowConfig.isOldRainbowWinStreak,
|
||
usesRainbowWinStreakUI: RainbowConfig.usesRainbowWinStreakUI });
|
||
let resets = 0, stoppedRevive = 0;
|
||
const cc = {
|
||
v2: (x, y) => ({ x, y }), Sprite: { SizeMode: { RAW: 2 } },
|
||
tween: () => ({ to() { return this; }, start() {} }),
|
||
fx: { GameConfig: config, AudioManager: { _instance: { playEffect() {} } },
|
||
GameTool: { getUserPowerTime: () => infinite, shushu_Track() {}, setWinStreak: () => resets++ } },
|
||
};
|
||
const nodes = prefab.map(data => data.__type__ === 'cc.Node' ? {
|
||
name: data._name, active: data._active, width: data._contentSize.width, height: data._contentSize.height,
|
||
x: data._trs.array[0], y: data._trs.array[1], scaleX: data._trs.array[7], scaleY: data._trs.array[8],
|
||
children: [], sprite: {},
|
||
getChildByName(name) { return this.children.find(n => n.name === name); },
|
||
getComponent() { return this.sprite; },
|
||
setPosition(x, y) { this.x = typeof x === 'object' ? x.x : x; this.y = typeof x === 'object' ? x.y : y; },
|
||
setScale(value) { this.scaleX = this.scaleY = value; },
|
||
setContentSize({ width, height }) { this.width = width; this.height = height; },
|
||
} : null);
|
||
nodes.forEach((node, i) => { if (node) node.children = prefab[i]._children.map(ref => nodes[ref.__id__]); });
|
||
const root = nodes[1], lose = root.getChildByName('lose');
|
||
const confirmation = loadModule('assets/Script/GiveUpConfirmation.ts', {}, { cc });
|
||
const Panel = loadMembers('assets/lose/script/LosePanel.ts', ['showGiveUpConfirmation'], { cc, ...confirmation });
|
||
const panel = Object.assign(new Panel(), { node: root, giveUpFrames: frameData.map(data => ({
|
||
uuid: data.uuid, getOriginalSize: () => ({ width: data.rawWidth, height: data.rawHeight }),
|
||
})) });
|
||
root.getComponent = () => panel;
|
||
const MapController = loadMembers('assets/Script/Map.ts',
|
||
['homeBtn', 'openHealth', 'closeWinStreak', 'hideLoseReasonPanels'], { cc, ...confirmation });
|
||
const map = Object.assign(new MapController(), {
|
||
node: { active: true }, homeCanTouch: true, reviewState: false, isMapRuntimeAlive: () => true,
|
||
getLoseRoot: () => root, trackFinishi() {},
|
||
revive: { getComponent: () => ({ offShow: () => stoppedRevive++ }) },
|
||
});
|
||
map.hideLoseReasonPanels(lose);
|
||
lose.getChildByName('Time').active = true;
|
||
return { panel, map, lose, config, setInfinite: value => infinite = value,
|
||
resets: () => resets, stoppedRevive: () => stoppedRevive };
|
||
}
|
||
|
||
test('all six title frames are serialized in giveup1–6 order', () => {
|
||
const component = prefab.find(data => data.__type__ === '0fbfcSygcRKl6AshXSeUHTO');
|
||
assert.deepEqual(component.giveUpFrames.map(ref => ref.__uuid__), frameData.map(data => data.uuid));
|
||
});
|
||
|
||
for (const rainbow of [false, true]) {
|
||
test(`all eight loss combinations reuse the ${rainbow ? 'rainbow' : 'normal'} panel without stale icons`, () => {
|
||
const { panel, lose } = fixture();
|
||
const cases = [
|
||
[true, true, true, 3], [true, true, false, 2], [true, false, false, 1],
|
||
[false, true, false, 4], [false, true, true, 5], [false, false, true, 6],
|
||
[true, false, true, 1], [false, false, false, null],
|
||
];
|
||
for (const [hp, day, streak, titleNumber] of cases) {
|
||
assert.equal(panel.showGiveUpConfirmation(hp, day, streak, rainbow), titleNumber !== null);
|
||
const current = lose.getChildByName(rainbow ? 'WinStreak2' : 'WinStreak');
|
||
const other = lose.getChildByName(rainbow ? 'WinStreak' : 'WinStreak2');
|
||
assert.equal(other.active, false);
|
||
assert.equal(current.active, titleNumber !== null);
|
||
if (!titleNumber) continue;
|
||
const icons = ['hp', 'day', 'winStreak'].map(name => current.getChildByName(name));
|
||
assert.deepEqual(icons.map(icon => icon.active), [hp, day, streak]);
|
||
const title = current.getChildByName('result_title');
|
||
assert.equal(title.sprite.spriteFrame.uuid, frameData[titleNumber - 1].uuid);
|
||
assert.equal(title.sprite.trim, false);
|
||
assert.equal(title.width / title.height, frameData[titleNumber - 1].rawWidth / frameData[titleNumber - 1].rawHeight);
|
||
const visible = icons.filter(icon => icon.active);
|
||
for (const icon of visible) {
|
||
assert.equal(icon.scaleX, icon.scaleY, 'icons retain their aspect ratio');
|
||
assert.ok(icon.y - icon.height * icon.scaleY / 2 > -263, 'icons stay above the think-again button');
|
||
}
|
||
if (visible.length === 1) assert.equal(visible[0].x, 0);
|
||
if (visible.length >= 2) {
|
||
assert.ok(visible[0].x < 0 && visible[1].x > 0);
|
||
assert.ok(visible[0].y >= visible[1].y);
|
||
}
|
||
if (visible.length === 3) assert.ok(visible[2].x === 0 && visible[2].y < visible[0].y);
|
||
}
|
||
});
|
||
}
|
||
|
||
test('A/B/C mainline unlock boundaries and help levels select the correct panels and losses', () => {
|
||
const cases = [
|
||
[{ level: 34 }, [true, false, false]], [{ level: 35 }, [true, true, false]],
|
||
[{ level: 49 }, [true, true, false]], [{ level: 50 }, [true, true, true]],
|
||
[{ level: 50, streak: 9 }, [true, true, false]],
|
||
[{ group: 'A', level: 0, streak: 0 }, [true, true, false]],
|
||
[{ infinite: true }, [false, true, true]],
|
||
[{ help: 1 }, [true, false, false]],
|
||
[{ help: 1, infinite: true }, null], [{ level: 34, infinite: true }, null],
|
||
];
|
||
for (const rainbowVariant of [1, 2, 3]) for (const [options, expected] of cases) {
|
||
const rainbow = rainbowVariant !== 1;
|
||
const f = fixture({ ...options, rainbowVariant });
|
||
const before = JSON.stringify(f.config.GM_INFO);
|
||
f.map.homeBtn(null, 'time');
|
||
const current = f.lose.getChildByName(rainbow ? 'WinStreak2' : 'WinStreak');
|
||
assert.equal(current.active, !!expected, JSON.stringify(options));
|
||
assert.equal(f.lose.getChildByName('Health').active, !expected);
|
||
assert.equal(JSON.stringify(f.config.GM_INFO), before, 'display does not mutate reward or task state');
|
||
if (expected) {
|
||
assert.deepEqual(['hp', 'day', 'winStreak'].map(name => current.getChildByName(name).active), expected);
|
||
assert.equal(f.resets(), 0, 'opening confirmation must not clear a streak');
|
||
assert.equal(f.stoppedRevive(), 0, 'revive remains available until exit is confirmed');
|
||
} else {
|
||
assert.equal(f.lose.getChildByName('Health').getChildByName('node2').active, true);
|
||
}
|
||
}
|
||
});
|
||
|
||
test('cancel keeps the revive screen available; confirm opens Health with the current stamina state', () => {
|
||
for (const rainbowVariant of [1, 2, 3]) for (const help of [0, 1]) for (const infinite of [false, true]) {
|
||
const f = fixture({ help, rainbowVariant });
|
||
f.map.homeBtn(null, 'time');
|
||
f.map.closeWinStreak();
|
||
assert.equal(f.map.homeCanTouch, true);
|
||
assert.equal(f.lose.getChildByName('Time').active, true);
|
||
assert.equal(f.resets(), 0);
|
||
f.map.homeBtn(null, 'time');
|
||
f.setInfinite(infinite);
|
||
f.map.openHealth();
|
||
const health = f.lose.getChildByName('Health');
|
||
assert.equal(health.active, true);
|
||
assert.equal(health.getChildByName('node1').active, !infinite);
|
||
assert.equal(health.getChildByName('node2').active, infinite);
|
||
assert.equal(f.lose.getChildByName('WinStreak').active, false);
|
||
assert.equal(f.lose.getChildByName('WinStreak2').active, false);
|
||
assert.equal(f.lose.getChildByName('Time').active, false);
|
||
assert.equal(f.resets(), help ? 0 : 1, 'assist exits must not clear the player streak');
|
||
}
|
||
});
|