Files
tea-sdlc/test/sdlc-report-assets.test.js
T
jiantw83andClaude Opus 5 b56216e0f0 test(工時報表): 釘住週次歸屬的跨月、跨年與五個週五
時區在測試裡固定為 Asia/Taipei:週界是以人在的時區切的,不釘住時區就等於沒釘住答案。
--today 讓「本週」在 CLI 接縫上釘得住,否則預設期間會跟著系統時鐘漂走。

寫入請求那則斷言誠實列出唯一的非 GET——四層前置檢查的寫入權探針,
而不是先把它濾掉再宣稱整趟沒有寫入。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:50:35 +00:00

110 lines
4.3 KiB
JavaScript
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 test from 'node:test';
import assert from 'node:assert/strict';
import {
assertNeutralPrompt,
assertTemplateSections,
readPrompt,
readTemplate,
} from './helpers/prompt-doc.js';
const template = readTemplate('report');
const prompt = readPrompt('sdlc-report');
/** 報表的四個段落,順序即印出來的順序 */
const SECTIONS = ['總計', '分段小計', '逐議題', '附註'];
// ── 輸出模板 ───────────────────────────────────────────────────────
test('模板依序包含四個段落', () => {
assertTemplateSections(template, SECTIONS);
});
test('模板以 {{變數}} 佔位,不留任何空白待填欄位', () => {
const placeholders = [...template.matchAll(/\{\{([^}]+)\}\}/g)].map((m) => m[1]);
assert.ok(placeholders.length >= SECTIONS.length, '每個段落至少要有一個佔位');
for (const name of placeholders) {
assert.match(name, /^[a-z一-龥]+$/u, `佔位名稱 ${name} 應為單一詞,不含空白或符號`);
}
});
test('模板不含邏輯:沒有任何條件或迴圈語法', () => {
assert.equal(/\{\{[#/^]/.test(template), false, '分段與逐議題由呼叫端展開,模板不做迴圈');
});
test('總計把實際、估算與落差擺在同一列,落差不必自己算', () => {
const totals = template.slice(template.indexOf('## 總計'), template.indexOf('## 分段小計'));
for (const placeholder of ['{{實際工時}}', '{{估算人天}}', '{{已估實際}}', '{{落差}}']) {
assert.ok(totals.includes(placeholder), `總計缺少 ${placeholder}`);
}
});
// ── 流程正本 ───────────────────────────────────────────────────────
test('正本平台中立,description 前綴正確', () => {
assertNeutralPrompt(prompt, 'sdlc-report');
});
test('正本交代三種期間,並指明預設是本週', () => {
assert.match(prompt, /--week/);
assert.match(prompt, /--month YYYY-MM/);
assert.match(prompt, /--year YYYY/);
assert.match(prompt, /預設/);
});
test('正本釘住期間定義的三句話', () => {
assert.match(prompt, /一週為週一至週日/);
assert.match(prompt, /該週週五所屬月份/);
assert.match(prompt, /W1[–-]W5.*第幾個週五/s);
});
test('正本以實例說明跨月與跨年那一週落在哪邊', () => {
assert.match(prompt, /2025-12-29/, '跨年的例子要寫出具體日期,規則才驗得出來');
assert.match(prompt, /2026-02-01/, '跨月的例子要寫出具體日期');
});
test('正本指名由 report.js 取數字,並要求直接用算好的時分', () => {
assert.match(prompt, /report\.js/);
assert.match(prompt, /不要自己再乘|已經算好/);
});
test('正本明令只印在終端,不張貼到任何管道', () => {
assert.match(prompt, /只印在終端/);
assert.match(prompt, /不(要)?張貼/);
});
test('正本說明落差的正負方向,以及一人天等於幾小時', () => {
assert.match(prompt, /正數.*超出估算/);
assert.match(prompt, /8\s*小時/);
assert.match(prompt, /--day-hours/);
});
test('正本交代總計的落差只涵蓋有估算的議題', () => {
assert.match(prompt, /總計的落差只涵蓋有估算的議題/);
assert.match(prompt, /已估實際秒/, '要指名分子是哪一個欄位,否則會被讀成全部實際');
});
test('正本交代沒寫估算時落差是空的,不是零', () => {
assert.match(prompt, /不是零|非零|空的/);
assert.match(prompt, /null/);
});
test('正本交代略過的工時筆數要講出來', () => {
assert.match(prompt, /略過/);
});
test('正本的邊界寫明這是唯讀流程', () => {
const boundary = prompt.slice(prompt.indexOf('## 邊界'));
assert.match(boundary, /不寫入|只發 GET/);
assert.match(boundary, /不補登|不動碼錶/);
});
test('正本交代週報沒有分段小計時整段不印', () => {
assert.match(prompt, /週報沒有分段/);
});