commenting-visual-explainers/tests/shared.unit.test.ts
2026-03-20 13:30:40 +09:00

130 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { slugify, generateId } from '../src/shared/slug';
import { fmtTime } from '../src/shared/time';
import { PRIORITY_CYCLE, PRIORITY_COLORS, HIGHLIGHT_COLORS } from '../src/shared/constants';
import { authHeaders } from '../src/shared/api-client';
import { mapNormToOrig } from '../src/widget/highlight';
describe('slugify', () => {
test('HTTP プレフィックスを除去する', () => {
expect(slugify('https://example.com/page')).toBe('example_com_page');
});
test('日本語文字を保持する', () => {
const result = slugify('https://example.com/テスト');
expect(result).toContain('テスト');
});
test('100文字で切り詰める', () => {
const long = 'https://example.com/' + 'a'.repeat(200);
expect(slugify(long).length).toBe(100);
});
test('記号をアンダースコアに変換する', () => {
expect(slugify('https://a.b/c?d=e&f=g')).toBe('a_b_c_d_e_f_g');
});
});
describe('generateId', () => {
test('文字列を返す', () => {
expect(typeof generateId()).toBe('string');
});
test('一意性がある100回生成してユニーク', () => {
const ids = new Set(Array.from({ length: 100 }, () => generateId()));
expect(ids.size).toBe(100);
});
});
describe('fmtTime', () => {
let realNow: () => number;
beforeEach(() => {
realNow = Date.now;
vi.spyOn(Date, 'now').mockReturnValue(1700000000000);
});
afterEach(() => {
vi.restoreAllMocks();
});
test('1分未満は「たった今」', () => {
expect(fmtTime(1700000000000 - 30000)).toBe('たった今');
});
test('1時間未満は「N分前」', () => {
expect(fmtTime(1700000000000 - 300000)).toBe('5分前');
});
test('24時間未満は「N時間前」', () => {
expect(fmtTime(1700000000000 - 7200000)).toBe('2時間前');
});
test('24時間以上は日時文字列', () => {
const result = fmtTime(1700000000000 - 86400000 * 2);
expect(result).toMatch(/\d+月/);
});
});
describe('PRIORITY_CYCLE', () => {
test('must → better → want → must の循環', () => {
expect(PRIORITY_CYCLE.must).toBe('better');
expect(PRIORITY_CYCLE.better).toBe('want');
expect(PRIORITY_CYCLE.want).toBe('must');
});
test('3つの優先度が全て定義されている', () => {
expect(Object.keys(PRIORITY_CYCLE)).toEqual(['must', 'better', 'want']);
});
});
describe('PRIORITY_COLORS', () => {
test('3つの優先度が全て定義されている', () => {
expect(Object.keys(PRIORITY_COLORS)).toEqual(['must', 'better', 'want']);
});
test('各色に bg, text, light, border がある', () => {
for (const p of ['must', 'better', 'want'] as const) {
expect(PRIORITY_COLORS[p]).toHaveProperty('bg');
expect(PRIORITY_COLORS[p]).toHaveProperty('text');
expect(PRIORITY_COLORS[p]).toHaveProperty('light');
expect(PRIORITY_COLORS[p]).toHaveProperty('border');
}
});
});
describe('HIGHLIGHT_COLORS', () => {
test('3つの優先度が全て定義されている', () => {
expect(Object.keys(HIGHLIGHT_COLORS)).toEqual(['must', 'better', 'want']);
});
});
describe('authHeaders', () => {
test('トークンありの場合 Authorization を含む', () => {
const h = authHeaders('my-token');
expect(h['Authorization']).toBe('Bearer my-token');
expect(h['Content-Type']).toBe('application/json');
});
test('トークンなしの場合 Authorization を含まない', () => {
const h = authHeaders('');
expect(h['Authorization']).toBeUndefined();
expect(h['Content-Type']).toBe('application/json');
});
});
describe('mapNormToOrig', () => {
test('空白なしの単純マッチ', () => {
expect(mapNormToOrig('hello world', 0, 5)).toEqual([0, 5]);
});
test('余分な空白がある場合の正規化マッチ', () => {
// 正規化後のインデックス 6-11 は orig で 6-12 に対応(余分な空白を含む)
expect(mapNormToOrig('hello world', 6, 11)).toEqual([6, 12]);
});
test('範囲外の normStart は null を返す', () => {
expect(mapNormToOrig('short', 100, 105)).toBeNull();
});
});