Commit 2d0e8878 authored by nanahira's avatar nanahira

yrp tests

parent 8fcdf430
Pipeline #42893 failed with stages
in 60 minutes and 7 seconds
......@@ -89,3 +89,38 @@ https://cdn02.moecube.com:444/ygopro-super-pre/versions/master/test-release-v2.j
- 通知相关人员进行整合到 8888 服务器。
本方法测试的 BUG 进度在表格的「内核更新测试记录」标签页追踪。
### 自动化测试
进行测试之后,请把相关的 yrp 录像文件放置在 `tests/yrp` 目录内。日后系统自动化测试将会运行这些 yrp 回放文件,避免后续重复脚本故障。
如果 yrp 是使用残局运行的,那么请把对应的残局文件放在 `tests/single` 目录内(请不要改残局名字)。
#### 环境准备
如果需要做 yrp 固化或者运行测试本身,那么需要在本目录内准备下列文件,并准备好 Node.js 环境,运行 `npm ci` 安装必要 js 依赖,确保测试框架正常运行。
- `ygopro/cards.cdb`
- `ygopro/script`
不需要额外放入超先行卡数据或者 ypk。超先行卡数据会从根目录读取。
#### yrp 固化
默认情况下,测试 yrp 只测试「YRP 是否能正常运行」,不会测试「能否复现特定的游戏状态」。如果需要测试特定的游戏状态,请使用 `freezeyrp` 脚本把 yrp 固化。
```bash
npm run freezeyrp <name1> <name2> ...
```
例如
```bash
npm run freezeyrp sample
```
这会读取 `tests/yrp/sample.yrp` 文件,在项目内创建 `tests/yrp-info/sample.yaml` 文件,记录 `sample.yrp` 的游戏状态。之后每次运行测试时,系统会把 `sample.yrp` 固化成 `sample.yaml` 记录的状态,进行更为严格的检查。
#### jstest api
对于使用 jstest api 创建的 `spec.ts` 测试文件,请放在 `tests/specs` 目录内。系统会自动运行这些测试文件。
......@@ -9,12 +9,14 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"js-yaml": "^4.1.1",
"sql.js": "^1.13.0",
"ygopro-jstest": "^1.0.8",
"ygopro-jstest": "^1.0.10",
"ygopro-msg-encode": "^1.1.5"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.2.1",
"@types/sql.js": "^1.4.9",
"@typescript-eslint/eslint-plugin": "^8.54.0",
......@@ -25,6 +27,7 @@
"jest": "^30.2.0",
"prettier": "^3.8.1",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
},
......@@ -59,7 +62,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
......@@ -545,6 +547,30 @@
"dev": true,
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@emnapi/core": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
......@@ -1376,6 +1402,34 @@
"@sinonjs/commons": "^3.0.1"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
......@@ -1476,6 +1530,13 @@
"pretty-format": "^30.0.0"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz",
......@@ -1554,7 +1615,6 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
......@@ -2035,7 +2095,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
......@@ -2053,6 +2112,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
......@@ -2129,11 +2201,17 @@
"node": ">= 8"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/babel-jest": {
......@@ -2315,7 +2393,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
......@@ -2617,6 +2694,13 @@
"integrity": "sha512-PDBv4l90xZKrUsZ0vtoycgZpO/j4iFsqJXrAxsyBDsnQRI7ZMJXIjgDJsKNjd5L8jnVnnlrDCdhkFbTncgCVjQ==",
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
......@@ -2692,6 +2776,16 @@
"node": ">=8"
}
},
"node_modules/diff": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
......@@ -2779,7 +2873,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
......@@ -2836,7 +2929,6 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
......@@ -3714,7 +3806,6 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
......@@ -4313,7 +4404,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
......@@ -4974,7 +5064,6 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
......@@ -5698,7 +5787,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -5805,6 +5893,50 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
......@@ -5864,7 +5996,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
......@@ -5975,6 +6106,13 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
......@@ -6247,9 +6385,9 @@
"license": "MIT"
},
"node_modules/ygopro-jstest": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/ygopro-jstest/-/ygopro-jstest-1.0.8.tgz",
"integrity": "sha512-Cwy6+nLwL8DVpMBUZSrFbVztmKkk9u8O9yQ8A0xzprHWJVrjTenLqjBklylcqKaBr3JBzeRD2/lG/fHiWjMH1A==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/ygopro-jstest/-/ygopro-jstest-1.0.10.tgz",
"integrity": "sha512-6lj9zkdk4zkwTeG0kfScXOcUh0VJ7N3NXmoDuxRBW1TmQkBvxuAy6M96mEWMBxw55+o5XCC8wxZN+KmosWVj7w==",
"license": "MIT",
"dependencies": {
"cosmokit": "^1.8.1",
......@@ -6281,6 +6419,16 @@
"ygopro-deck-encode": "^1.0.15"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
......
......@@ -4,7 +4,8 @@
"version": "1.0.0",
"scripts": {
"lint": "eslint --fix .",
"test": "jest --passWithNoTests"
"test": "jest --passWithNoTests",
"freeze-yrp": "ts-node tests/tools/freeze-yrp.ts"
},
"repository": {
"type": "git",
......@@ -37,6 +38,7 @@
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.2.1",
"@types/sql.js": "^1.4.9",
"@typescript-eslint/eslint-plugin": "^8.54.0",
......@@ -47,11 +49,13 @@
"jest": "^30.2.0",
"prettier": "^3.8.1",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
},
"dependencies": {
"js-yaml": "^4.1.1",
"sql.js": "^1.13.0",
"ygopro-jstest": "^1.0.8",
"ygopro-jstest": "^1.0.10",
"ygopro-msg-encode": "^1.1.5"
}
}
Debug.SetAIName("as")
Debug.ReloadFieldBegin(DUEL_ATTACK_FIRST_TURN)
Debug.SetPlayerInfo(0,8000,0,0)
Debug.SetPlayerInfo(1,8000,0,0)
Debug.AddCard(28985331,0,0,LOCATION_HAND,0,POS_FACEUP)
Debug.AddCard(10000000,0,0,LOCATION_HAND,0,POS_FACEUP)
Debug.AddCard(5560911,0,0,LOCATION_DECK,0,POS_FACEDOWN)
Debug.AddCard(14558127,1,1,LOCATION_HAND,0,POS_FACEUP)
Debug.AddCard(73580471,0,0,LOCATION_EXTRA,0,POS_FACEDOWN)
Debug.ReloadFieldEnd()
import {
SlientAdvancor,
SummonPlaceAdvancor,
NoEffectAdvancor,
SelectCardAdvancor,
} from "ygopro-jstest";
import {
OcgcoreScriptConstants,
YGOProMsgSelectIdleCmd,
YGOProMsgDraw,
YGOProMsgSelectEffectYn,
YGOProMsgSelectChain,
} from "ygopro-msg-encode";
import { createTest } from "../utility/create-test";
describe("sample standalone spec", () => {
it("Should process duel", async () => {
await createTest({}, (ctx) =>
ctx
.addCard([
{
code: 28985331,
location: OcgcoreScriptConstants.LOCATION_HAND,
},
{
code: 10000000,
location: OcgcoreScriptConstants.LOCATION_HAND,
},
{
code: 5560911,
location: OcgcoreScriptConstants.LOCATION_DECK,
},
{
code: 14558127,
location: OcgcoreScriptConstants.LOCATION_HAND,
controller: 1,
},
{
code: 73580471,
location: OcgcoreScriptConstants.LOCATION_EXTRA,
},
])
.advance(SlientAdvancor())
.state(YGOProMsgSelectIdleCmd, (msg) => {
expect(
ctx.allMessages.find((m) => m instanceof YGOProMsgDraw),
).toBeUndefined(); // make sure it does not draw any card
const deck = ctx.getFieldCard(
0,
OcgcoreScriptConstants.LOCATION_DECK,
);
expect(deck).toHaveLength(1);
const ex = ctx.getFieldCard(0, OcgcoreScriptConstants.LOCATION_EXTRA);
expect(ex).toHaveLength(1);
const hand = ctx.getFieldCard(
0,
OcgcoreScriptConstants.LOCATION_HAND,
);
expect(hand).toHaveLength(2);
const oppHand = ctx.getFieldCard(
1,
OcgcoreScriptConstants.LOCATION_HAND,
);
expect(oppHand).toHaveLength(1);
const c1 = hand.find((c) => c.code === 28985331);
const c2 = hand.find((c) => c.code === 10000000);
expect(c1).toBeDefined();
expect(c2).toBeDefined();
expect(c1.canSummon()).toBe(true);
expect(c2.canSummon()).toBe(false);
expect(c1.canActivate()).toBe(false);
return c1.summon();
})
.advance(SummonPlaceAdvancor(), NoEffectAdvancor())
.state(YGOProMsgSelectEffectYn, (msg) => {
expect(msg.code).toBe(28985331); // check if it's the correct card effect
return msg.prepareResponse(true);
})
.state(YGOProMsgSelectChain, (msg) => {
const field = ctx.getFieldCard(
1,
OcgcoreScriptConstants.LOCATION_HAND,
);
expect(field).toHaveLength(1);
const c1 = field[0];
expect(c1.code).toBe(14558127);
expect(c1.canActivate()).toBe(true); // can activate urara
// does not activate urara here
})
.advance(SlientAdvancor(), SelectCardAdvancor({ code: 5560911 }))
.state(YGOProMsgSelectIdleCmd, (msg) => {
const grave = ctx.getFieldCard(
0,
OcgcoreScriptConstants.LOCATION_GRAVE,
);
expect(grave).toHaveLength(1);
expect(grave[0].code).toBe(5560911); // the deckdes monster should be in grave
expect(grave[0].canActivate()).toBe(true);
return grave[0].activate();
})
.advance(
SummonPlaceAdvancor(),
SelectCardAdvancor({ code: 28985331 }),
SlientAdvancor(),
)
.state(YGOProMsgSelectIdleCmd, (msg) => {
expect(ctx.getLP(0)).toBe(4000);
const mzone = ctx.getFieldCard(
0,
OcgcoreScriptConstants.LOCATION_MZONE,
);
expect(mzone).toHaveLength(2);
const warrior = mzone.find((c) => c.code === 28985331);
expect(warrior.level).toBe(4);
const dragon = mzone.find((c) => c.code === 5560911);
expect(dragon.level).toBe(3);
const ex = ctx.getFieldCard(0, OcgcoreScriptConstants.LOCATION_EXTRA);
expect(ex).toHaveLength(1);
expect(ex[0].code).toBe(73580471);
expect(ex[0].canSpecialSummon()).toBe(true);
return ex[0].specialSummon();
})
.advance(
SelectCardAdvancor({ code: 5560911 }, { code: 28985331 }),
SummonPlaceAdvancor(),
NoEffectAdvancor(),
)
.state(YGOProMsgSelectEffectYn, (msg) => msg.prepareResponse(true)) // activate effect to destroy itself
.advance(SlientAdvancor())
.state(YGOProMsgSelectIdleCmd, (msg) => {
const mzone = ctx.getFieldCard(
0,
OcgcoreScriptConstants.LOCATION_MZONE,
);
expect(mzone).toHaveLength(0); // destroyed
const grave = ctx.getFieldCard(
0,
OcgcoreScriptConstants.LOCATION_GRAVE,
);
expect(grave).toHaveLength(2); // dragon returned to deck, and black rose and warrior at grave
return;
}),
);
});
});
import path from "node:path";
import fs from "node:fs";
import { createTest } from "../utility/create-test";
import { toYrpInfo } from "../utility/yrp-info";
import yaml from "js-yaml";
async function main() {
const yrpFilenames = process.argv.slice(2);
if (yrpFilenames.length === 0) {
console.error("Usage: npm run yrpfreeze <replay1> <replay2> ...");
process.exit(1);
}
for (const yrpFilename of yrpFilenames) {
const fullPath = path.resolve(
process.cwd(),
"tests",
"yrp",
`${yrpFilename}.yrp`,
);
const destPath = path.resolve(
process.cwd(),
"tests",
"yrp-info",
`${yrpFilename}.yaml`,
);
console.log(`Will save YRP info from ${fullPath} to ${destPath}`);
await createTest({ yrp: fullPath }, async (test) => {
const info = toYrpInfo(test);
console.log(info.snapshotText);
const yamlStr = yaml.dump(info);
await fs.promises.writeFile(destPath, yamlStr, "utf-8");
console.log(`Saved YRP info from ${fullPath} to ${destPath}`);
});
}
}
main().then();
......@@ -11,6 +11,7 @@ export const createTest = (
ygoproPath: [
...(options.ygoproPath || []),
process.cwd(),
path.resolve(process.cwd(), "tests"),
path.resolve(process.cwd(), process.env.YGOPRO_PATH || "ygopro"),
],
},
......
import { formatSnapshot, YGOProTest } from "ygopro-jstest";
import { YGOProMsgBase } from "ygopro-msg-encode";
export type MsgSnapshot = {
identifier: number;
msg: string;
} & Partial<YGOProMsgBase>;
export interface YrpInfo {
messages: MsgSnapshot[];
snapshot: ReturnType<typeof YGOProTest.prototype.querySnapshot>;
snapshotText: string;
}
export const toYrpInfo = (test: YGOProTest): YrpInfo => {
const snapshot = test.querySnapshot();
const snapshotText = formatSnapshot(snapshot);
return {
messages: test.allMessages.map((msg) => ({
identifier: msg.identifier,
msg: msg.constructor.name,
...msg,
})),
snapshot,
snapshotText,
};
}
messages:
- identifier: 33
msg: YGOProMsgShuffleHand
player: 0
count: 2
cards:
- 28985331
- 10000000
- identifier: 41
msg: YGOProMsgNewPhase
phase: 1
- identifier: 41
msg: YGOProMsgNewPhase
phase: 2
- identifier: 41
msg: YGOProMsgNewPhase
phase: 4
- identifier: 11
msg: YGOProMsgSelectIdleCmd
player: 0
summonableCount: 1
summonableCards:
- code: 28985331
controller: 0
location: 2
sequence: 0
spSummonableCount: 0
spSummonableCards: []
reposableCount: 0
reposableCards: []
msetableCount: 1
msetableCards:
- code: 28985331
controller: 0
location: 2
sequence: 0
ssetableCount: 0
ssetableCards: []
activatableCount: 0
activatableCards: []
canBp: 1
canEp: 1
canShuffle: 1
snapshot:
cards:
- flags: 15712255
controller: 0
location: 1
sequence: 0
empty: false
code: 5560911
position: 10
alias: 5560911
type: 4129
level: 7
rank: 0
attribute: 32
race: 8192
attack: 1000
defense: 3000
baseAttack: 1000
baseDefense: 3000
reason: 0
targetCards: []
overlayCards: []
counters: []
owner: 0
status: 0
lscale: 0
rscale: 0
link: 0
linkMarker: 0
name: 亡龙之战栗-死欲龙
- flags: 15712255
controller: 0
location: 2
sequence: 0
empty: false
code: 28985331
position: 10
alias: 28985331
type: 33
level: 4
rank: 0
attribute: 32
race: 1
attack: 1400
defense: 1200
baseAttack: 1400
baseDefense: 1200
reason: 0
targetCards: []
overlayCards: []
counters: []
owner: 0
status: 0
lscale: 0
rscale: 0
link: 0
linkMarker: 0
name: 终末之骑士
- flags: 15712255
controller: 0
location: 2
sequence: 1
empty: false
code: 10000000
position: 10
alias: 10000000
type: 33
level: 10
rank: 0
attribute: 64
race: 2097152
attack: 4000
defense: 4000
baseAttack: 4000
baseDefense: 4000
reason: 0
targetCards: []
overlayCards: []
counters: []
owner: 0
status: 0
lscale: 0
rscale: 0
link: 0
linkMarker: 0
name: 欧贝利斯克之巨神兵
- flags: 15712255
controller: 0
location: 64
sequence: 0
empty: false
code: 73580471
position: 10
alias: 73580471
type: 8225
level: 7
rank: 0
attribute: 4
race: 8192
attack: 2400
defense: 1800
baseAttack: 2400
baseDefense: 1800
reason: 0
targetCards: []
overlayCards: []
counters: []
owner: 0
status: 0
lscale: 0
rscale: 0
link: 0
linkMarker: 0
name: 黑蔷薇龙
- flags: 15712255
controller: 1
location: 2
sequence: 0
empty: false
code: 14558127
position: 10
alias: 14558127
type: 4129
level: 3
rank: 0
attribute: 4
race: 16
attack: 0
defense: 1800
baseAttack: 0
baseDefense: 1800
reason: 0
targetCards: []
overlayCards: []
counters: []
owner: 1
status: 0
lscale: 0
rscale: 0
link: 0
linkMarker: 0
name: 灰流丽
lp:
- 8000
- 8000
chains: []
snapshotText: |-
********* Field Snapshot *********
LP: P0 8000 | P1 8000
********* Player 0 *********
Hand: 终末之骑士
欧贝利斯克之巨神兵
********* Player 1 *********
Hand: 灰流丽
********* Finish *********
This source diff could not be displayed because it is too large. You can view the blob instead.
import { existsSync, readdirSync } from "node:fs";
import { createTest } from "./utility/create-test";
import { MsgSnapshot, toYrpInfo, YrpInfo } from "./utility/yrp-info";
import yaml from "js-yaml";
import path from "node:path";
import fs from "node:fs";
describe("YRP", () => {
const yrpDirPath = path.resolve(process.cwd(), "tests", "yrp");
const yrpDir = readdirSync(yrpDirPath);
for (const yrpFilename of yrpDir) {
if (!yrpFilename.endsWith(".yrp")) continue;
const testName = `YRP: ${yrpFilename}`;
it(testName, async () => {
const yrpPath = path.resolve(yrpDirPath, yrpFilename);
await createTest({ yrp: yrpPath }, async (test) => {
const yrpInfoPath = path.resolve(
__dirname,
"yrp-info",
yrpFilename.slice(0, -4) + ".yaml",
);
const currentInfo = toYrpInfo(test);
const hasYrpInfo = existsSync(yrpInfoPath);
console.log(
`Testing YRP: ${yrpFilename}\nYRP info yaml: ${hasYrpInfo ? "available" : "none"}\n${currentInfo.snapshotText}`,
);
if (hasYrpInfo) {
// do further tests
const expectedInfo = (await yaml.load(
await fs.promises.readFile(yrpInfoPath, "utf-8"),
)) as YrpInfo;
expect(currentInfo.snapshot.lp).toEqual(expectedInfo.snapshot.lp);
expect(currentInfo.snapshot.chains).toEqual(
expectedInfo.snapshot.chains,
);
expect(currentInfo.snapshot.cards).toEqual(
expectedInfo.snapshot.cards,
);
const sortMesssages = (messages: MsgSnapshot[]) =>
messages
.filter((m) => !m.msg.includes("Hint"))
.map((m) => {
// go through all properties and prune every desc
const pruneDesc = <T>(obj: T, visited = new Set<any>()): T => {
if (typeof obj !== "object" || obj === null) return obj;
if (visited.has(obj)) return obj;
visited.add(obj);
if (Array.isArray(obj)) {
return obj.map((item) => pruneDesc(item, visited)) as any;
}
const newObj: any = {};
for (const key in obj) {
if (key !== "desc") {
newObj[key] = pruneDesc(obj[key], visited);
}
}
return newObj;
};
return pruneDesc(m);
});
expect(sortMesssages(currentInfo.messages)).toEqual(
sortMesssages(expectedInfo.messages),
);
}
});
});
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment