mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-08-03 05:46:06 +00:00
refactor(gh): CI用スクリプトをpackageとして整理 (#17727)
* refactor(gh): CI用スクリプトをpackageとして整理 * fix * fix * fix * fix * fix * fix * fix * remove old scripts * migrate * refactor 1 * refactor 2 * fix comment * fix * fix * fix * fix * remove vite-node from changelog-checker * fix lint * fix * refactor * update deps * fix * spec: rename packages
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import {
|
||||
calcAndFormatDeltaPercent,
|
||||
calcAndFormatDeltaPercentInMdTable,
|
||||
escapeHtml,
|
||||
escapeLatex,
|
||||
escapeMdTableCell,
|
||||
formatBytes,
|
||||
formatColoredDelta,
|
||||
formatKiBAsMb,
|
||||
formatMs,
|
||||
formatNumber,
|
||||
} from '../src/format';
|
||||
|
||||
describe('formatBytes', () => {
|
||||
// 1024ではなく1000区切り。単位が2桁以上なら小数を落とす
|
||||
test.each([
|
||||
[0, '0 B'],
|
||||
[999, '999 B'],
|
||||
[1_000, '1 KB'],
|
||||
[1_500, '1.5 KB'],
|
||||
[15_000, '15 KB'],
|
||||
[1_234_567, '1.2 MB'],
|
||||
[12_345_678, '12 MB'],
|
||||
[1_500_000_000, '1.5 GB'],
|
||||
[1_500_000_000_000, '1,500 GB'],
|
||||
])('formats %i as %s', (input, expected) => {
|
||||
expect(formatBytes(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatColoredDelta', () => {
|
||||
test('leaves zero uncoloured and unsigned', () => {
|
||||
expect(formatColoredDelta(0, formatNumber)).toBe('0');
|
||||
});
|
||||
|
||||
test('colours growth orange and shrinkage green', () => {
|
||||
expect(formatColoredDelta(5, formatNumber)).toBe('$\\color{orange}{\\text{+5}}$');
|
||||
expect(formatColoredDelta(-5, formatNumber)).toBe('$\\color{green}{\\text{-5}}$');
|
||||
});
|
||||
|
||||
test('omits colour below the threshold but keeps the sign', () => {
|
||||
expect(formatColoredDelta(5, formatNumber, 10)).toBe('$\\text{+5}$');
|
||||
});
|
||||
});
|
||||
|
||||
describe('escaping', () => {
|
||||
test('escapeHtml covers the five metacharacters', () => {
|
||||
expect(escapeHtml(`&<>"'`)).toBe('&<>"'');
|
||||
});
|
||||
|
||||
test('escapeHtml maps nullish to an empty string', () => {
|
||||
expect(escapeHtml(null)).toBe('');
|
||||
expect(escapeHtml(undefined)).toBe('');
|
||||
});
|
||||
|
||||
test('escapeLatex escapes braces and percent', () => {
|
||||
expect(escapeLatex('100% {x}')).toBe('100\\% \\{x\\}');
|
||||
});
|
||||
|
||||
test('escapeMdTableCell keeps pipes and newlines from breaking the table', () => {
|
||||
expect(escapeMdTableCell('a|b\nc')).toBe('a\\|b<br>c');
|
||||
});
|
||||
});
|
||||
|
||||
describe('percent helpers', () => {
|
||||
// before が0だと変化率そのものが定義できない
|
||||
test('returns a placeholder when the baseline is zero or missing', () => {
|
||||
expect(calcAndFormatDeltaPercent(0, 10)).toBe('-');
|
||||
expect(calcAndFormatDeltaPercent(null, 10)).toBe('-');
|
||||
expect(calcAndFormatDeltaPercent(10, null)).toBe('-');
|
||||
});
|
||||
|
||||
// 0になったのは「消えた」という有効な結果なので、隠さず -100% として出す
|
||||
test('formats a drop to zero as -100%', () => {
|
||||
expect(calcAndFormatDeltaPercent(10, 0)).toBe('$\\color{green}{\\text{-100\\%}}$');
|
||||
});
|
||||
|
||||
// Markdownのテーブルセル内ではLaTeXの \% がさらに食われるため二重にする
|
||||
test('doubles the percent escape for markdown table cells', () => {
|
||||
expect(calcAndFormatDeltaPercentInMdTable(100, 110)).toContain('\\\\%');
|
||||
expect(calcAndFormatDeltaPercent(100, 110)).not.toContain('\\\\%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nullish handling', () => {
|
||||
test('formatKiBAsMb and formatMs render a dash for missing values', () => {
|
||||
expect(formatKiBAsMb(null)).toBe('-');
|
||||
expect(formatKiBAsMb(Number.NaN)).toBe('-');
|
||||
expect(formatMs(undefined)).toBe('-');
|
||||
});
|
||||
|
||||
test('formatMs switches to seconds past 1000ms', () => {
|
||||
expect(formatMs(999)).toBe('999 ms');
|
||||
expect(formatMs(1_500)).toBe('1.5 s');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { html, joinHtml, raw, Raw } from '../src/html';
|
||||
|
||||
describe('html', () => {
|
||||
test('escapes interpolated values by default', () => {
|
||||
const value = '<script>alert(1)</script>';
|
||||
expect(String(html`<p>${value}</p>`)).toBe('<p><script>alert(1)</script></p>');
|
||||
});
|
||||
|
||||
test('escapes values used in attribute position', () => {
|
||||
const url = 'x"><script>alert(1)</script>';
|
||||
expect(String(html`<a href="${url}"></a>`)).toBe('<a href="x"><script>alert(1)</script>"></a>');
|
||||
});
|
||||
|
||||
test('leaves the static parts of the template untouched', () => {
|
||||
expect(String(html`<p class="a">x</p>`)).toBe('<p class="a">x</p>');
|
||||
});
|
||||
|
||||
test('renders nullish values as an empty string', () => {
|
||||
expect(String(html`<p>${null}${undefined}</p>`)).toBe('<p></p>');
|
||||
});
|
||||
|
||||
test('does not double-escape nested fragments', () => {
|
||||
const inner = html`<b>${'a&b'}</b>`;
|
||||
expect(String(html`<p>${inner}</p>`)).toBe('<p><b>a&b</b></p>');
|
||||
});
|
||||
|
||||
// 配列をそのまま文字列化するとカンマ区切りで潰れる。実際に過去これで表が壊れた
|
||||
test('joins arrays without separators instead of stringifying them', () => {
|
||||
const items = [html`<li>1</li>`, html`<li>2</li>`];
|
||||
expect(String(html`<ul>${items}</ul>`)).toBe('<ul><li>1</li><li>2</li></ul>');
|
||||
});
|
||||
|
||||
test('escapes array elements that are not fragments', () => {
|
||||
expect(String(html`<p>${['<a>', '<b>']}</p>`)).toBe('<p><a><b></p>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('raw', () => {
|
||||
test('embeds trusted markup without escaping', () => {
|
||||
expect(String(html`<style>${raw('a > b { color: red }')}</style>`)).toBe('<style>a > b { color: red }</style>');
|
||||
});
|
||||
|
||||
test('produces a Raw fragment', () => {
|
||||
expect(raw('x')).toBeInstanceOf(Raw);
|
||||
expect(html`x`).toBeInstanceOf(Raw);
|
||||
});
|
||||
});
|
||||
|
||||
describe('joinHtml', () => {
|
||||
test('joins fragments with the given separator', () => {
|
||||
expect(String(joinHtml([html`<li>1</li>`, html`<li>2</li>`], '\n'))).toBe('<li>1</li>\n<li>2</li>');
|
||||
});
|
||||
|
||||
test('returns an empty fragment for an empty list', () => {
|
||||
expect(String(joinHtml([], '\n'))).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { finiteMedian, mad, median, pairedDeltaSummary, sampleSpread } from '../src/stats';
|
||||
|
||||
describe('median', () => {
|
||||
test('takes the middle value of an odd-length sample', () => {
|
||||
expect(median([3, 1, 2])).toBe(2);
|
||||
});
|
||||
|
||||
// 偶数長では平均を整数に丸める。KiB単位の整数値を扱う前提のため
|
||||
test('rounds the average of an even-length sample', () => {
|
||||
expect(median([1, 2])).toBe(2);
|
||||
expect(median([1, 4])).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mad', () => {
|
||||
test('measures the median absolute deviation', () => {
|
||||
expect(mad([1, 1, 1])).toBe(0);
|
||||
expect(mad([1, 2, 3])).toBe(1);
|
||||
});
|
||||
|
||||
test('refuses to compute from a single sample', () => {
|
||||
expect(() => mad([1])).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('finiteMedian', () => {
|
||||
test('ignores non-finite entries', () => {
|
||||
expect(finiteMedian([1, null, undefined, Number.NaN, 3])).toBe(2);
|
||||
});
|
||||
|
||||
test('returns null by default when nothing is finite', () => {
|
||||
expect(finiteMedian([null, undefined])).toBeNull();
|
||||
});
|
||||
|
||||
test('returns the supplied default when nothing is finite', () => {
|
||||
expect(finiteMedian([null, undefined], 0)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSpread', () => {
|
||||
test('needs at least two finite samples', () => {
|
||||
expect(sampleSpread([1])).toBeNull();
|
||||
expect(sampleSpread([1, null])).toBeNull();
|
||||
expect(sampleSpread([1, 3])).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pairedDeltaSummary', () => {
|
||||
const base = [
|
||||
{ round: 1, value: 100 },
|
||||
{ round: 2, value: 200 },
|
||||
{ round: 3, value: 300 },
|
||||
];
|
||||
|
||||
test('compares base and head within the same round', () => {
|
||||
const head = [
|
||||
{ round: 1, value: 110 },
|
||||
{ round: 2, value: 230 },
|
||||
{ round: 3, value: 320 },
|
||||
];
|
||||
|
||||
expect(pairedDeltaSummary(base, head, sample => sample.value)).toStrictEqual({
|
||||
median: 20,
|
||||
mad: 10,
|
||||
min: 10,
|
||||
max: 30,
|
||||
samples: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test('drops rounds that only one side has', () => {
|
||||
const head = [
|
||||
{ round: 1, value: 110 },
|
||||
{ round: 2, value: 230 },
|
||||
{ round: 9, value: 999 },
|
||||
];
|
||||
|
||||
expect(pairedDeltaSummary(base, head, sample => sample.value).samples).toBe(2);
|
||||
});
|
||||
|
||||
// 1サンプルでも中央値・最小・最大は定まる (偏差は常に0)
|
||||
test('summarizes a single paired round without treating MAD as an error', () => {
|
||||
expect(pairedDeltaSummary([base[0]], [{ round: 1, value: 130 }], sample => sample.value)).toStrictEqual({
|
||||
median: 30,
|
||||
mad: 0,
|
||||
min: 30,
|
||||
max: 30,
|
||||
samples: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('fails loudly when no round is shared', () => {
|
||||
expect(() => pairedDeltaSummary(base, [{ round: 9, value: 1 }], sample => sample.value)).toThrow(/no rounds in common/);
|
||||
});
|
||||
|
||||
// 負のroundはwarmupを表すので集計に混ぜない
|
||||
test('ignores warmup rounds', () => {
|
||||
const warmupBase = [{ round: -1, value: 0 }, ...base];
|
||||
const warmupHead = [{ round: -1, value: 9999 }, { round: 1, value: 110 }, { round: 2, value: 230 }, { round: 3, value: 320 }];
|
||||
|
||||
expect(pairedDeltaSummary(warmupBase, warmupHead, sample => sample.value).samples).toBe(3);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user