Skip to content

Commit

Permalink
test: 完善测试覆盖率
Browse files Browse the repository at this point in the history
  • Loading branch information
HoPGoldy committed Sep 23, 2021
1 parent 9030675 commit 585c85f
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 15 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/MERGE.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ jobs:

- name: Test
run: yarn run test-unit

- name: Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
COVERALLS_SERVICE_NAME: github-action
COVERALLS_GIT_BRANCH: screeps-world-printer
run: yarn run coveralls
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,6 @@ dist

.screeps-world-printer/

.unit-test-*
.unit-test-*

.coveralls.yml
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![DRAW_OFFICAL](https://github.com/HoPGoldy/screeps-world-printer/actions/workflows/DRAW_OFFICAL.yml/badge.svg?event=schedule)](https://github.com/HoPGoldy/screeps-world-printer/releases)
[![npm](https://img.shields.io/npm/v/screeps-world-printer)](https://www.npmjs.com/package/screeps-world-printer)
[![Coverage Status](https://coveralls.io/repos/github/HoPGoldy/screeps-world-printer/badge.svg?branch=main)](https://coveralls.io/github/HoPGoldy/screeps-world-printer?branch=main)

screeps-world-printer 是一个简单且支持自定义的 screeps 游戏地图绘制工具。

Expand Down
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
{
"name": "screeps-world-printer",
"version": "0.1.2",
"description": "简单且灵活的 screeps 游戏地图绘制工具",
"version": "0.1.3",
"scripts": {
"build": "tsc --project tsconfig.build.json",
"lint": "eslint src/** && tsc --noEmit",
"test-unit": "jest --coverage"
"test-unit": "jest --coverage",
"coveralls": "coveralls < coverage/lcov.info"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": "git@github.com:HoPGoldy/screeps-world-printer.git",
"author": "HoPGoldy <hpgless@outlook.com>",
"license": "MIT",
"keywords": ["screeps", "map", "draw", "node", "typescript"],
"keywords": [
"screeps",
"map",
"draw",
"node",
"typescript"
],
"peerDependencies": {
"sharp": "^0.28.3"
},
Expand All @@ -31,6 +39,7 @@
"@types/sharp": "^0.28.4",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"coveralls": "^3.1.1",
"eslint": "^7.32.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.24.2",
Expand Down
8 changes: 8 additions & 0 deletions src/cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ test('可以读写头像', async () => {

// 模拟一下正常使用,不能出现报错
await expect(useBuffer(existGetter)).resolves.toBeUndefined();

// 此时就可以直接访问到缓存
const newBadgeGetter = await cache.createBadgeGetter(playerInfo);
expect(newBadgeGetter).not.toBeUndefined();
});

test('可以读写地图瓦片', async () => {
Expand All @@ -80,6 +84,10 @@ test('可以读写地图瓦片', async () => {

// 模拟一下正常使用,不能出现报错
await expect(useBuffer(existGetter)).resolves.toBeUndefined();

// 此时就可以直接访问到缓存
const newRoomGetter = await cache.createRoomGetter(roomName);
expect(newRoomGetter).not.toBeUndefined();
});

test('可以读写地图行缓存', async () => {
Expand Down
66 changes: 66 additions & 0 deletions src/drawRoom.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { defaultRoomDrawer } from './drawRoom';
import { DrawMaterial, RoomStatus } from './type';

test('输入 undefined 返回 undefined', async () => {
const result = await defaultRoomDrawer(undefined);
expect(result).toBeUndefined();
});

const getSvgBuffer = async (): Promise<Buffer> => Buffer.from(
'<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150"></svg>'
);

test('可以正确使用头像访问器', async () => {
const getBadge = jest.fn(getSvgBuffer);
const getBadgeBorder = jest.fn(getSvgBuffer);

// 没有玩家占领,不应该调用头像访问器
const material: DrawMaterial = {
roomName: '',
roomInfo: { status: RoomStatus.Normal },
getMask: getSvgBuffer,
getRoom: getSvgBuffer,
getBadge,
getBadgeBorder
};

await defaultRoomDrawer(material);

expect(getBadge).not.toHaveBeenCalled();
expect(getBadgeBorder).not.toHaveBeenCalled();

// 玩家占领,应该调用头像访问器
material.roomInfo.own = { user: '', level: 0 };

await defaultRoomDrawer(material);

expect(getBadge).toBeCalledTimes(1);
expect(getBadgeBorder).toBeCalledTimes(1);
});

test('可以正常使用蒙版访问器', async () => {
const getMask = jest.fn(getSvgBuffer);

const material: DrawMaterial = {
roomName: '',
roomInfo: { status: RoomStatus.Normal },
getMask,
getRoom: getSvgBuffer
};

await defaultRoomDrawer(material);
expect(getMask).not.toHaveBeenCalledWith();

const maskTypes = [
RoomStatus.Inactivated,
RoomStatus.Novice,
RoomStatus.Respawn
];

for (const maskType of maskTypes) {
getMask.mockClear();
material.roomInfo.status = maskType;
await defaultRoomDrawer(material);
expect(getMask).toHaveBeenCalledWith(maskType);
}
});
4 changes: 2 additions & 2 deletions src/drawRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ const maskProcessor: DrawProcessor = async function (roomTile, material) {
* 渲染房间对应的头像,以及对头像进行缩放和透明度处理
*/
const badgeProcessor: DrawProcessor = async function (roomTile, material) {
if (!material.getBadge || !material.getBadgeBorder) return undefined;
if (!material.roomInfo.own || !material.getBadge || !material.getBadgeBorder) return undefined;

// 将头像和边框贴起来
const rawBadge = await material.getBadge();
const badgeBorder = await material.getBadgeBorder();
const badgeWithBorder = sharp(rawBadge).composite([{ input: badgeBorder, blend: 'atop' }]);

const { width: rawBadgeWidth } = await badgeWithBorder.metadata();
const ownLevel = material.roomInfo.own?.level;
const ownLevel = material.roomInfo.own.level;

// level 有可能为 0,所以需要特判一下
if (!rawBadgeWidth || ownLevel === undefined) {
Expand Down
93 changes: 93 additions & 0 deletions src/printer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */

import { MapStatsResp, PlayerBadge, RoomStatus } from './type';
import { ScreepsWorldPrinter } from './printer';
import { ScreepsService } from './service';

// 默认的 mock 地图信息
const DEFAULT_MAP_STATS: MapStatsResp = {
stats: {
E0N0: { status: RoomStatus.Normal, own: { user: 'asd123', level: 2 } },
W0N0: { status: RoomStatus.Inactivated },
W0S0: { status: RoomStatus.Novice },
E0S0: { status: RoomStatus.Respawn }
},
users: {
asd123: { _id: 'asd123', username: 'testUser', badge: {} as PlayerBadge }
}
};

const getSvgBuffer = async (): Promise<Buffer> => Buffer.from(
'<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150"></svg>'
);

const mockService = (service: ScreepsService) => {
const connect = jest.fn();
const getBadge = jest.fn(getSvgBuffer);
const getRoomTile = jest.fn(getSvgBuffer);
const getMapSize = jest.fn(async () => ({ width: 2, height: 2 }));
const getMapStats = jest.fn(async () => JSON.parse(JSON.stringify(DEFAULT_MAP_STATS)));
service.connect = connect;
service.getBadge = getBadge;
service.getMapSize = getMapSize;
service.getMapStats = getMapStats;
service.getRoomTile = getRoomTile;

return { connect, getBadge, getMapStats, getMapSize, getRoomTile };
};

test('可以正常下载内容', async () => {
const printer = new ScreepsWorldPrinter({
host: 'testHost',
shard: 'shard3',
token: 'testToken'
});

mockService(printer.service);
printer.silence();

const worldDataSet = await printer.fetchWorld();
expect(worldDataSet).toHaveProperty('roomMaterialMatrix');
expect(worldDataSet).toHaveProperty('mapSize');
expect(worldDataSet).toHaveProperty('roomStats');
});

test('可以设置房间名解析器', async () => {
const printer = new ScreepsWorldPrinter({
host: 'testHost',
shard: 'shard3',
token: 'testToken'
}, async () => [['W0N0']]);

mockService(printer.service);
printer.silence();

const worldDataSet = await printer.fetchWorld();
expect(worldDataSet.roomMaterialMatrix.length).toBe(1);
expect(worldDataSet.roomMaterialMatrix[0].length).toBe(1);

printer.setRoomNameGetter(async () => [[undefined, 'W0N0', 'W0S0']]);
const newWorldDataSet = await printer.fetchWorld();
expect(newWorldDataSet.roomMaterialMatrix.length).toBe(1);
expect(newWorldDataSet.roomMaterialMatrix[0].length).toBe(3);
// 因为新设置的房间名第一个是个 undefined,所以这里也会返回 undefined
expect(newWorldDataSet.roomMaterialMatrix[0][0]).toBeUndefined();
});

test('可以正常执行绘制', async () => {
const printer = new ScreepsWorldPrinter({
host: 'testHost',
shard: 'shard3',
token: 'testToken'
});

mockService(printer.service);

const saver = jest.fn(async () => 'save path');
printer.setResultSaver(saver);

await printer.drawWorld();

expect(saver).toBeCalledTimes(1);
});
88 changes: 88 additions & 0 deletions src/service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-function-return-type */

import { AxiosInstance } from 'axios';
import { ServerConnectInfo } from '.';
import { ScreepsService } from './service';

const createService = function (opt: ServerConnectInfo) {
const service = new ScreepsService(opt);
const getSpy = jest.fn();
const postSpy = jest.fn();
const http = (service as any).http as AxiosInstance;
http.get = getSpy;
http.post = postSpy;

return { service, http, getSpy, postSpy };
};

test('可以登录', async () => {
const { service, http } = createService({
host: 'testHost',
shard: 'shard3',
token: 'testToken'
});

// 正确配置了服务器 url
expect(http.defaults.baseURL).toBe('testHost');

await service.connect();
expect(http.defaults.headers['X-Token']).toBe('testToken');
expect(http.defaults.headers['X-Username']).toBe('testToken');

const { service: privateService, http: privateHttp, postSpy } = createService({
host: 'testPrivate',
username: 'test',
password: '123'
});
postSpy.mockResolvedValueOnce({ data: { token: 'returnedToken' } });

await privateService.connect();
expect(postSpy.mock.calls[0][1]).toEqual({ email: 'test', password: '123' });
expect(privateHttp.defaults.headers['X-Token']).toBe('returnedToken');
expect(privateHttp.defaults.headers['X-Username']).toBe('returnedToken');
});

test('可以下载地图瓦片', async () => {
const { service, getSpy } = createService({
host: 'testHost',
shard: 'shard3',
token: 'testToken'
});

const tile = Buffer.from(
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"></svg>'
);

getSpy.mockResolvedValue({ data: tile });
const result = await service.getRoomTile('roomName');
// 发起的请求中包含房间名
expect(getSpy.mock.calls[0][0]).toContain('roomName');
// 可以正常接受到瓦片
expect(result).toBe(tile);
});

test('可以下载头像', async () => {
const { service, getSpy } = createService({
host: 'testHost',
shard: 'shard3',
token: 'testToken'
});

// 原始头像
const badBadge = `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">
<circle cx="50" cy="50" r="52" />
</svg>`;

// 修复后的头像
const goodBadge = `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">
<circle cx="50" cy="50" r="50" />
</svg>`;

getSpy.mockResolvedValue({ data: badBadge });
const result = await service.getBadge('username');
// 发起的请求中包含玩家名
expect(getSpy.mock.calls[0][0]).toContain('username');
// 可以正常接收到头像并且进行了修复
expect(result.toString()).toBe(goodBadge);
});
5 changes: 3 additions & 2 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class ScreepsService {
this.roomTileCdn = opt.roomTileCdn ?? DEFAULT_ROOM_TILE_CDN;
this.shard = opt.shard;
}
else if ('username' in this.connectInfo && 'password' in this.connectInfo) {
else if ('username' in opt && 'password' in opt) {
await this.login(opt.username, opt.password);
}
else throw new Error('无效的连接方式');
Expand Down Expand Up @@ -123,7 +123,8 @@ export class ScreepsService {
? `${base}/map/${this.shard}/${roomName}.png`
: `${base}/assets/map/${roomName}.png`;

const roomTile = await axios.get<Buffer>(fullPath, {
const roomTile = await this.http.get<Buffer>(fullPath, {
baseURL: '',
timeout: DEFAULT_TIMEOUT,
responseType: 'arraybuffer'
});
Expand Down

0 comments on commit 585c85f

Please sign in to comment.