@sun-asterisk/sungen 3.2.34-beta.1 → 3.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +3 -29
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +12 -2
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +1 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +2 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/expanded-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/date-picker-unsupported.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +13 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +9 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/expanded-assertion.hbs +12 -0
- package/dist/generators/test-generator/code-generator.js +4 -5
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +9 -3
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +9 -3
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +8 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +52 -8
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +16 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +59 -0
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +10 -2
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +2 -13
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/next-step.d.ts +0 -2
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +1 -15
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +3 -6
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +4 -9
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +9 -11
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +1 -7
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +3 -13
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +8 -38
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/commands.js +5 -3
- package/dist/orchestrator/ai-skills/commands.js.map +1 -1
- package/dist/orchestrator/ai-skills/golden-skills.test.js +29 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +1 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +52 -3
- package/dist/orchestrator/flow-manager.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +52 -4
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +26 -4
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +22 -30
- package/dist/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/dist/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +24 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +0 -18
- package/dist/orchestrator/templates/specs-test-data.ts +22 -4
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +1 -4
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -1
- package/dist/orchestrator/viewpoint-pattern-catalog.js +2 -5
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +3 -26
- package/src/cli/index.ts +0 -2
- package/src/exporters/test-data-resolver.ts +10 -2
- package/src/generators/gherkin-parser/index.ts +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +2 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/expanded-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/date-picker-unsupported.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +13 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +9 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/expanded-assertion.hbs +12 -0
- package/src/generators/test-generator/code-generator.ts +4 -4
- package/src/generators/test-generator/diagnostics.ts +9 -3
- package/src/generators/test-generator/patterns/types.ts +2 -0
- package/src/generators/test-generator/step-mapper.ts +56 -9
- package/src/generators/test-generator/utils/data-resolver.ts +53 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +10 -2
- package/src/harness/audit.ts +2 -13
- package/src/harness/next-step.ts +1 -17
- package/src/harness/parse.ts +5 -13
- package/src/harness/sensors.ts +8 -9
- package/src/harness/viewpoint-baseline.ts +1 -7
- package/src/harness/viewpoint-ledger.ts +11 -52
- package/src/index.ts +2 -1
- package/src/orchestrator/ai-skills/commands.ts +5 -3
- package/src/orchestrator/ai-skills/golden-skills.test.ts +23 -0
- package/src/orchestrator/flow-manager.ts +53 -3
- package/src/orchestrator/screen-manager.ts +52 -4
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +26 -4
- package/src/orchestrator/templates/ai-src/commands/create-test.md +22 -30
- package/src/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/src/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +24 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +0 -18
- package/src/orchestrator/templates/specs-test-data.ts +22 -4
- package/src/orchestrator/viewpoint-pattern-catalog.ts +2 -5
- package/dist/cli/commands/viewpoint.d.ts +0 -7
- package/dist/cli/commands/viewpoint.d.ts.map +0 -1
- package/dist/cli/commands/viewpoint.js +0 -70
- package/dist/cli/commands/viewpoint.js.map +0 -1
- package/dist/harness/viewpoint-draft-header.d.ts +0 -7
- package/dist/harness/viewpoint-draft-header.d.ts.map +0 -1
- package/dist/harness/viewpoint-draft-header.js +0 -100
- package/dist/harness/viewpoint-draft-header.js.map +0 -1
- package/dist/harness/viewpoint-mode.d.ts +0 -9
- package/dist/harness/viewpoint-mode.d.ts.map +0 -1
- package/dist/harness/viewpoint-mode.js +0 -52
- package/dist/harness/viewpoint-mode.js.map +0 -1
- package/dist/harness/viewpoint-priority.d.ts +0 -19
- package/dist/harness/viewpoint-priority.d.ts.map +0 -1
- package/dist/harness/viewpoint-priority.js +0 -36
- package/dist/harness/viewpoint-priority.js.map +0 -1
- package/dist/harness/viewpoint-review-open.d.ts +0 -6
- package/dist/harness/viewpoint-review-open.d.ts.map +0 -1
- package/dist/harness/viewpoint-review-open.js +0 -90
- package/dist/harness/viewpoint-review-open.js.map +0 -1
- package/dist/harness/viewpoint-scan-refs.d.ts +0 -50
- package/dist/harness/viewpoint-scan-refs.d.ts.map +0 -1
- package/dist/harness/viewpoint-scan-refs.js +0 -200
- package/dist/harness/viewpoint-scan-refs.js.map +0 -1
- package/dist/harness/viewpoint-scan.d.ts +0 -41
- package/dist/harness/viewpoint-scan.d.ts.map +0 -1
- package/dist/harness/viewpoint-scan.js +0 -118
- package/dist/harness/viewpoint-scan.js.map +0 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +0 -63
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +0 -158
- package/dist/orchestrator/viewpoint-scaffold.d.ts +0 -3
- package/dist/orchestrator/viewpoint-scaffold.d.ts.map +0 -1
- package/dist/orchestrator/viewpoint-scaffold.js +0 -70
- package/dist/orchestrator/viewpoint-scaffold.js.map +0 -1
- package/src/cli/commands/viewpoint.ts +0 -58
- package/src/harness/viewpoint-draft-header.ts +0 -55
- package/src/harness/viewpoint-mode.ts +0 -49
- package/src/harness/viewpoint-priority.ts +0 -38
- package/src/harness/viewpoint-review-open.ts +0 -51
- package/src/harness/viewpoint-scan-refs.ts +0 -154
- package/src/harness/viewpoint-scan.ts +0 -111
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +0 -63
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +0 -158
- package/src/orchestrator/viewpoint-scaffold.ts +0 -36
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which create-test mode a unit's test-viewpoint.md calls for — the table in create-test.md,
|
|
3
|
-
* computed so the agent reads it from `sungen viewpoint scan` instead of inferring it.
|
|
4
|
-
*
|
|
5
|
-
* mode 2 (AI drafts / re-reviews its draft): file missing · untouched scaffold · first line
|
|
6
|
-
* `drafted-by: ai` · VP rows exist but none has a Kỳ vọng
|
|
7
|
-
* mode 1 (review only, never edit): anything else — a QA-written file, with or without VP ids
|
|
8
|
-
*
|
|
9
|
-
* "Untouched scaffold" is checked on RAW lines against the template the scaffold is written from:
|
|
10
|
-
* apart from the first `# ` line (the title), every non-blank line must be a template line —
|
|
11
|
-
* comments included, so a QA note inside `<!-- -->` or under its own `# ` heading makes the file
|
|
12
|
-
* the QA's (owner decision 2026-10-14: a QA file is never overwritten). Every doubt falls to
|
|
13
|
-
* mode 1: a scaffold from an older sungen (other template lines) or an unreadable template.
|
|
14
|
-
*/
|
|
15
|
-
import { isAiDraft } from './viewpoint-draft-header';
|
|
16
|
-
import { viewpointScaffold } from '../orchestrator/viewpoint-scaffold';
|
|
17
|
-
|
|
18
|
-
export type ViewpointMode = 1 | 2;
|
|
19
|
-
|
|
20
|
-
/** Trimmed non-blank lines without the title (the first `# ` line), BOM and CRLF ignored. */
|
|
21
|
-
function bodyLines(text: string): string[] {
|
|
22
|
-
const lines = text.replace(/^/, '').replace(/\r\n?/g, '\n').split('\n').map((l) => l.trim()).filter(Boolean);
|
|
23
|
-
const title = lines.findIndex((l) => l.startsWith('# '));
|
|
24
|
-
if (title >= 0) lines.splice(title, 1);
|
|
25
|
-
return lines;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let templateLines: Set<string> | null = null;
|
|
29
|
-
function isUntouchedScaffold(raw: string): boolean {
|
|
30
|
-
if (!templateLines) {
|
|
31
|
-
try {
|
|
32
|
-
templateLines = new Set([false, true].flatMap((mobile) => bodyLines(viewpointScaffold('', mobile))));
|
|
33
|
-
} catch {
|
|
34
|
-
return false; // no template to compare with → treat the file as the QA's
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const known = templateLines;
|
|
38
|
-
return bodyLines(raw).every((l) => known.has(l));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** `raw` is the file as written (null when missing); `rows` are its VP rows from the scan. */
|
|
42
|
-
export function viewpointMode(raw: string | null, rows: { hasExpected: boolean }[]): { mode: ViewpointMode; reason: string } {
|
|
43
|
-
if (raw === null) return { mode: 2, reason: 'file missing' };
|
|
44
|
-
if (isAiDraft(raw)) return { mode: 2, reason: 'drafted-by: ai (re-review)' };
|
|
45
|
-
if (rows.length > 0) {
|
|
46
|
-
return rows.some((r) => r.hasExpected) ? { mode: 1, reason: 'QA-written' } : { mode: 2, reason: 'VP rows without Kỳ vọng' };
|
|
47
|
-
}
|
|
48
|
-
return isUntouchedScaffold(raw) ? { mode: 2, reason: 'untouched scaffold' } : { mode: 1, reason: 'QA-written, no VP ids' };
|
|
49
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One reading of a priority — a viewpoint row's cell or a scenario's tag.
|
|
3
|
-
*
|
|
4
|
-
* The overview parser, the ledger's uniform-priority check and PRIORITY-CONTRADICTED each read a
|
|
5
|
-
* priority their own way (`/high/i` substrings, raw words, a one-off medium→normal), so the same
|
|
6
|
-
* row could be `Medium` to one and `Unknown` to another — `Med`, which QA writes, was `Unknown`.
|
|
7
|
-
* The vocabulary is `high | normal | low`; `critical`, `medium` and `med` are read as aliases.
|
|
8
|
-
*/
|
|
9
|
-
export type PriorityLevel = 'high' | 'normal' | 'low' | 'deferred' | 'skip' | 'unknown';
|
|
10
|
-
|
|
11
|
-
const WORD: Record<string, PriorityLevel> = {
|
|
12
|
-
critical: 'high', high: 'high',
|
|
13
|
-
medium: 'normal', med: 'normal', normal: 'normal',
|
|
14
|
-
low: 'low', deferred: 'deferred',
|
|
15
|
-
};
|
|
16
|
-
/** "Declared not applicable" — one level, so every consumer checks one word. */
|
|
17
|
-
const NOT_APPLICABLE = /^(?:skip(?:ped)?|n\/a|none|not applicable)$/;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A priority cell or tag → its level. The whole cell being a word is the clean case; otherwise the
|
|
21
|
-
* overview has always read prose by SUBSTRING in precedence order (`High — complex form`,
|
|
22
|
-
* `Medium-High`), and keeps doing so. The substring words stay unbounded for `high|medium|low`
|
|
23
|
-
* (existing behaviour); the newer `critical|normal|med` need word boundaries (`Medical` is not `Med`).
|
|
24
|
-
*/
|
|
25
|
-
export function parsePriority(cell: string): PriorityLevel {
|
|
26
|
-
const w = cell.replace(/[*_`]/g, '').replace(/\.+$/, '').trim().toLowerCase();
|
|
27
|
-
if (WORD[w]) return WORD[w];
|
|
28
|
-
if (NOT_APPLICABLE.test(w)) return 'skip';
|
|
29
|
-
if (/high|\bcritical\b/.test(w)) return 'high';
|
|
30
|
-
if (/medium|\bnormal\b|\bmed\b/.test(w)) return 'normal';
|
|
31
|
-
if (/low/.test(w)) return 'low';
|
|
32
|
-
return 'unknown';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** The public `ViewpointEntry.priority` spelling of a level — the exported type is unchanged. */
|
|
36
|
-
export function toEntryPriority(level: PriorityLevel): 'High' | 'Medium' | 'Low' | 'Unknown' {
|
|
37
|
-
return level === 'high' ? 'High' : level === 'normal' ? 'Medium' : level === 'low' ? 'Low' : 'Unknown';
|
|
38
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VIEWPOINT-REVIEW-OPEN (info) — the audit's one line about `requirements/viewpoint-review.md`.
|
|
3
|
-
*
|
|
4
|
-
* create-test reviews test-viewpoint.md with the sungen-viewpoint checklist and writes each point a
|
|
5
|
-
* QA should look at as a table row whose last cell is `[ ]`; the QA ticks it `[x]` or deletes the
|
|
6
|
-
* row. While rows stay unticked the audit says how many, once. Advisory only: no score, no gate, no
|
|
7
|
-
* baseline effect. A missing or unreadable file says nothing — the review is optional input.
|
|
8
|
-
*/
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import { stripHtmlComments } from './read-text';
|
|
12
|
-
|
|
13
|
-
export const VIEWPOINT_REVIEW_FILE = 'viewpoint-review.md';
|
|
14
|
-
|
|
15
|
-
const SEPARATOR = /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/;
|
|
16
|
-
/** A table separator, not a `---` rule under a row. */
|
|
17
|
-
const isSeparator = (line: string): boolean => SEPARATOR.test(line) && line.includes('|');
|
|
18
|
-
const UNTICKED = /^\[\s?\]$/;
|
|
19
|
-
|
|
20
|
-
/** The last cell of a markdown table row, or null for a line that is not one. */
|
|
21
|
-
function lastCell(line: string): string | null {
|
|
22
|
-
const t = line.trim();
|
|
23
|
-
if (!t.startsWith('|') || !t.endsWith('|') || t.length < 2) return null;
|
|
24
|
-
const cells = t.slice(1, -1).split(/(?<!\\)\|/);
|
|
25
|
-
return cells[cells.length - 1].trim();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Body rows whose last cell is `[ ]`. The header row (`… | [ ] |` above `|---|`) is not a point. */
|
|
29
|
-
export function openReviewPoints(text: string): number {
|
|
30
|
-
const lines = stripHtmlComments(text.replace(/\r\n?/g, '\n')).split('\n');
|
|
31
|
-
let open = 0;
|
|
32
|
-
for (let i = 0; i < lines.length; i++) {
|
|
33
|
-
if (isSeparator(lines[i]) || isSeparator(lines[i + 1] ?? '')) continue;
|
|
34
|
-
const last = lastCell(lines[i]);
|
|
35
|
-
if (last !== null && UNTICKED.test(last)) open++;
|
|
36
|
-
}
|
|
37
|
-
return open;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** The info finding for one unit's `requirements/` directory; empty when nothing is open. */
|
|
41
|
-
export function viewpointReviewOpenFindings(requirementsDir: string): string[] {
|
|
42
|
-
let text: string;
|
|
43
|
-
try {
|
|
44
|
-
text = fs.readFileSync(path.join(requirementsDir, VIEWPOINT_REVIEW_FILE), 'utf-8');
|
|
45
|
-
} catch {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
const open = openReviewPoints(text);
|
|
49
|
-
if (open === 0) return [];
|
|
50
|
-
return [`VIEWPOINT-REVIEW-OPEN (info): ${open} open point(s) in requirements/${VIEWPOINT_REVIEW_FILE} — the checklist review of test-viewpoint.md is waiting for QA. QA: fix the viewpoint where you agree, then tick the row \`[x]\` or delete it. Not scored; an agent does not tick rows for the QA.`];
|
|
51
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Building blocks of `sungen viewpoint scan` (viewpoint-scan.ts): the markdown table walker, the
|
|
3
|
-
* item-reference reader and the item universe. Pure, read-only, format-tolerant by design — a
|
|
4
|
-
* QA-written file is never judged here, only its ids and item numbers are read.
|
|
5
|
-
*/
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as path from 'path';
|
|
8
|
-
import { readTextFile, stripHtmlComments } from './read-text';
|
|
9
|
-
import { parseUiInventory } from './ui-inventory';
|
|
10
|
-
import { parseSpecClauses } from './spec-coverage';
|
|
11
|
-
|
|
12
|
-
export interface TableRow {
|
|
13
|
-
/** 1-based line in the (comment-stripped, newline-preserving) text. */
|
|
14
|
-
line: number;
|
|
15
|
-
raw: string;
|
|
16
|
-
/** Cells with `**`, backticks and edge spaces removed. */
|
|
17
|
-
cells: string[];
|
|
18
|
-
/** The table's header cells; null for a table with no separator line. */
|
|
19
|
-
header: string[] | null;
|
|
20
|
-
/** Text of every heading enclosing the row (outermost first) — where a `# MANUAL/ENV` mark may sit. */
|
|
21
|
-
section: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const SEPARATOR = /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/;
|
|
25
|
-
const splitCells = (line: string): string[] => line.trim().replace(/^\|/, '').replace(/\|$/, '')
|
|
26
|
-
.split(/(?<!\\)\|/).map((c) => c.replace(/\*\*|`/g, '').trim());
|
|
27
|
-
|
|
28
|
-
/** Every body row of every markdown table, with its header and enclosing headings. */
|
|
29
|
-
export function tableRows(text: string): TableRow[] {
|
|
30
|
-
const lines = text.split('\n');
|
|
31
|
-
const out: TableRow[] = [];
|
|
32
|
-
const headings: string[] = []; // index = level − 1
|
|
33
|
-
let header: string[] | null = null;
|
|
34
|
-
for (let i = 0; i < lines.length; i++) {
|
|
35
|
-
const raw = lines[i];
|
|
36
|
-
const h = raw.match(/^(#{1,6})\s+(.*)$/);
|
|
37
|
-
if (h) { headings.length = h[1].length - 1; headings[h[1].length - 1] = h[2]; header = null; continue; }
|
|
38
|
-
if (!raw.includes('|')) { header = null; continue; }
|
|
39
|
-
if (SEPARATOR.test(raw)) continue;
|
|
40
|
-
if (i + 1 < lines.length && SEPARATOR.test(lines[i + 1])) { header = splitCells(raw); continue; }
|
|
41
|
-
out.push({ line: i + 1, raw, cells: splitCells(raw), header, section: headings.join(' ') });
|
|
42
|
-
}
|
|
43
|
-
return out;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const NUM = String.raw`(?<!\d)\d{1,3}(?:\.\d{1,3})*(?![\d])`;
|
|
47
|
-
const LIST = new RegExp(`(?:\\bitems?|項目|(?<![\\w-])UI[\\s-]*#?|#)\\s*(${NUM}(?:\\s*(?:[-–—~〜~,;&/]|(?<![\\p{L}\\p{N}])(?:và|and)(?![\\p{L}\\p{N}]))\\s*${NUM})*)`, 'giu');
|
|
48
|
-
|
|
49
|
-
/** `009` → `9`, `4.03` → `4.3`; null when the text is not a plain (dotted) number. */
|
|
50
|
-
export function normRef(s: string): string | null {
|
|
51
|
-
const t = s.replace(/[[\]§#]/g, '').trim();
|
|
52
|
-
return /^\d+(\.\d+)*$/.test(t) ? t.split('.').map((p) => String(Number(p))).join('.') : null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The refs strictly between `a` and `b` (normalised): `4`–`6` → `5`, `5.4`–`5.8` → `5.5 … 5.7`.
|
|
57
|
-
* Only the last level steps, so both ends need the same prefix; `5.4–6.2` or `3–5.2` cannot be
|
|
58
|
-
* enumerated without the item list and give nothing.
|
|
59
|
-
*/
|
|
60
|
-
function between(a: string, b: string): string[] {
|
|
61
|
-
const pa = a.split('.');
|
|
62
|
-
const pb = b.split('.');
|
|
63
|
-
if (pa.length !== pb.length || pa.slice(0, -1).join('.') !== pb.slice(0, -1).join('.')) return [];
|
|
64
|
-
const lo = Number(pa[pa.length - 1]);
|
|
65
|
-
const hi = Number(pb[pb.length - 1]);
|
|
66
|
-
if (!(hi > lo && hi - lo < 200)) return [];
|
|
67
|
-
const prefix = pa.slice(0, -1).map((p) => `${p}.`).join('');
|
|
68
|
-
return Array.from({ length: hi - lo - 1 }, (_, k) => `${prefix}${lo + 1 + k}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Item numbers a cell cites: `item 9`, `item 2, 3, 7`, `item 4–6`, `item 5.4–5.8`, `項目 4.3〜4.6`, `UI #9`,
|
|
73
|
-
* `UI-009`, `[9]`, `§4.3.2`. Prose (`Auth guard`), message codes (`M02`) and question ids
|
|
74
|
-
* (`Q-01`) are not refs.
|
|
75
|
-
*/
|
|
76
|
-
export function extractRefs(text: string): string[] {
|
|
77
|
-
const refs = new Set<string>();
|
|
78
|
-
const add = (s: string): void => { const r = normRef(s); if (r) refs.add(r); };
|
|
79
|
-
for (const m of text.matchAll(LIST)) {
|
|
80
|
-
let prev: string | null = null;
|
|
81
|
-
let range = false;
|
|
82
|
-
for (const tok of m[1].matchAll(new RegExp(`(${NUM})|([-–—~〜~])`, 'g'))) {
|
|
83
|
-
if (tok[2]) { range = prev !== null; continue; }
|
|
84
|
-
const n = normRef(tok[1]);
|
|
85
|
-
if (!n) continue;
|
|
86
|
-
if (range && prev) for (const r of between(prev, n)) refs.add(r);
|
|
87
|
-
refs.add(n); prev = n; range = false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
for (const m of text.matchAll(/\[(\d{1,3})\]/g)) add(m[1]);
|
|
91
|
-
for (const m of text.matchAll(/§\s*(\d{1,3}(?:\.\d{1,3})*)/g)) add(m[1]);
|
|
92
|
-
return [...refs];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export type ItemSource = 'spec-ids' | 'spec-table' | 'spec-ids+spec-table' | 'ui-inventory' | 'none';
|
|
96
|
-
export interface UniverseItem { ref: string; name: string }
|
|
97
|
-
/** Every list a citation could point at, each in its own notation. */
|
|
98
|
-
export interface ItemUniverses { specIds: UniverseItem[]; specTable: UniverseItem[]; inventory: UniverseItem[] }
|
|
99
|
-
|
|
100
|
-
const NO_COL = /^(no\.?|#|stt|№|番号)$/i;
|
|
101
|
-
const TYPE_COL = /種別|\b(type|kind)\b|loại/i;
|
|
102
|
-
const NAME_COL = /item|name|tên|項目|名|field|label|trường|thành phần|component/i;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The unit's lists, by where each id is DECLARED: requirement ids spec.md declares (`**FR-BC-03**:`),
|
|
106
|
-
* the spec's item table (`No` + name + type columns), and ui-inventory ids (`UI-NNN`). An id's shape
|
|
107
|
-
* says nothing about which list it belongs to — `item 9` on a mockup and `UI-009` in the inventory
|
|
108
|
-
* number different things.
|
|
109
|
-
*/
|
|
110
|
-
export function itemUniverses(reqDir: string): ItemUniverses {
|
|
111
|
-
const read = (f: string): string | null => {
|
|
112
|
-
const p = path.join(reqDir, f);
|
|
113
|
-
return fs.existsSync(p) ? stripHtmlComments(readTextFile(p)) : null;
|
|
114
|
-
};
|
|
115
|
-
const out: ItemUniverses = { specIds: [], specTable: [], inventory: [] };
|
|
116
|
-
const spec = read('spec.md');
|
|
117
|
-
if (spec) {
|
|
118
|
-
out.specIds = parseSpecClauses(path.join(reqDir, 'spec.md')).frs.map((f) => ({ ref: f.id, name: f.text.slice(0, 80) }));
|
|
119
|
-
for (const row of tableRows(spec)) {
|
|
120
|
-
const h = row.header;
|
|
121
|
-
const no = h?.findIndex((c) => NO_COL.test(c)) ?? -1;
|
|
122
|
-
const type = h?.findIndex((c) => TYPE_COL.test(c)) ?? -1;
|
|
123
|
-
const name = h?.findIndex((c, i) => i !== no && i !== type && NAME_COL.test(c)) ?? -1;
|
|
124
|
-
if (no < 0 || type < 0 || name < 0) continue;
|
|
125
|
-
const ref = normRef(row.cells[no] ?? '');
|
|
126
|
-
if (ref) out.specTable.push({ ref, name: row.cells[name] ?? '' });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const inv = read('ui-inventory.md');
|
|
130
|
-
if (inv) {
|
|
131
|
-
out.inventory = parseUiInventory(inv).items.flatMap((i) => {
|
|
132
|
-
const ref = normRef(i.id.replace(/^UI-/i, ''));
|
|
133
|
-
return ref ? [{ ref, name: i.item }] : [];
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
return out;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* The list(s) the viewpoint actually cites — a list nobody cites says nothing about gaps. The spec's
|
|
141
|
-
* ids and its item table, each when cited (both when both are: the notations cannot collide, and
|
|
142
|
-
* dropping one would under-report); the table also when nothing is cited at all (then every item is
|
|
143
|
-
* uncited, which is true); the inventory only when the viewpoint writes `UI-NNN`; else none.
|
|
144
|
-
*/
|
|
145
|
-
export function citedUniverse(u: ItemUniverses, cited: ReadonlySet<string>, writesInventoryIds: boolean): { source: ItemSource; items: UniverseItem[] } {
|
|
146
|
-
const hits = (items: UniverseItem[]): boolean => items.some((i) => cited.has(i.ref));
|
|
147
|
-
const ids = hits(u.specIds);
|
|
148
|
-
const table = u.specTable.length > 0 && (cited.size === 0 || hits(u.specTable));
|
|
149
|
-
if (ids && table) return { source: 'spec-ids+spec-table', items: [...u.specIds, ...u.specTable] };
|
|
150
|
-
if (ids) return { source: 'spec-ids', items: u.specIds };
|
|
151
|
-
if (table) return { source: 'spec-table', items: u.specTable };
|
|
152
|
-
if (writesInventoryIds && hits(u.inventory)) return { source: 'ui-inventory', items: u.inventory };
|
|
153
|
-
return { source: 'none', items: [] };
|
|
154
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `sungen viewpoint scan` — the code half of reviewing a QA-written test-viewpoint.md.
|
|
3
|
-
*
|
|
4
|
-
* It does set arithmetic on ids and nothing else: which VP rows exist, what each cites, which spec
|
|
5
|
-
* items nobody cites, which ids no scenario carries. It never judges meaning, never edits the file,
|
|
6
|
-
* and feeds no audit finding, score or baseline.
|
|
7
|
-
*
|
|
8
|
-
* A VP row is a markdown table row whose FIRST cell is a full id (`VP-UI-001`, `FL-LOGIN-3`).
|
|
9
|
-
* Priority-table rows (`VP-LOGIN (Auth)`), bullets and appendix text are not rows. This is
|
|
10
|
-
* deliberately NOT `parseViewpointItems` (viewpoint-ledger.ts), which also reads id-less rows.
|
|
11
|
-
*/
|
|
12
|
-
import * as fs from 'fs';
|
|
13
|
-
import * as path from 'path';
|
|
14
|
-
import { readViewpointText, readTextFile } from './read-text';
|
|
15
|
-
import { featureFilesFor } from './unit-paths';
|
|
16
|
-
import { tableRows, extractRefs, itemUniverses, citedUniverse, ItemSource } from './viewpoint-scan-refs';
|
|
17
|
-
import { viewpointMode, ViewpointMode } from './viewpoint-mode';
|
|
18
|
-
|
|
19
|
-
export interface VpRow {
|
|
20
|
-
id: string;
|
|
21
|
-
line: number;
|
|
22
|
-
/** Item numbers the row cites (normalised: `009` → `9`), and spec-declared ids it cites (`FR-BC-03`). */
|
|
23
|
-
refs: string[];
|
|
24
|
-
/** `# MANUAL/ENV` or `@manual` on the row or an enclosing heading. */
|
|
25
|
-
manual: boolean;
|
|
26
|
-
/** The Kỳ vọng / Expected cell has text (best effort when the table has no such header). */
|
|
27
|
-
hasExpected: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ViewpointScan {
|
|
31
|
-
/** The create-test mode this file calls for (viewpoint-mode.ts): 2 = AI drafts, 1 = review only. */
|
|
32
|
-
mode: ViewpointMode;
|
|
33
|
-
modeReason: string;
|
|
34
|
-
vpRows: VpRow[];
|
|
35
|
-
/** Distinct VP ids among `vpRows` — a reused id is several rows but one id. */
|
|
36
|
-
distinctIds: number;
|
|
37
|
-
rowsWithoutExpected: string[];
|
|
38
|
-
manualRows: string[];
|
|
39
|
-
itemSource: ItemSource;
|
|
40
|
-
/** Spec/inventory items no VP row cites — a hint for the review, not a verdict. */
|
|
41
|
-
uncitedItems: { ref: string; name: string }[];
|
|
42
|
-
/** VP ids no feature file mentions; null when the unit has no feature file yet. */
|
|
43
|
-
withoutScenario: string[] | null;
|
|
44
|
-
/** Why `vpRows` is empty, else null. */
|
|
45
|
-
note: string | null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const VP_ID = /^(VP|FL)-[A-Z][A-Z0-9]*(-[A-Z0-9]+)*-\d+$/;
|
|
49
|
-
const MANUAL = /\bMANUAL\/ENV\b|@manual\b/i;
|
|
50
|
-
const EXPECTED_COL = /kỳ vọng|kết quả mong đợi|expected|期待/i;
|
|
51
|
-
/** Whole-word match (Unicode-aware: `\b` fails on `ồ`) so `Resource` / `Datasource` are not a source column. */
|
|
52
|
-
const SOURCE_COL = /(?<![\p{L}\p{N}])(?:nguồn|source|tham chiếu|ref|出典|根拠)(?![\p{L}\p{N}])/iu;
|
|
53
|
-
const PRIORITY = /^(critical|high|med(?:ium)?|low|cao|trung bình|thấp)$/i;
|
|
54
|
-
const BLANK = /^[\s\-–—…]*$/;
|
|
55
|
-
const SPEC_ID = /(?<![\w-])[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-\d+[A-Z]?(?![\w-])/gi;
|
|
56
|
-
export const NO_VP_IDS_NOTE = 'file has no VP ids; review falls back to AI-only';
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* VP rows of one viewpoint text (already comment-stripped, as `readViewpointText` returns it).
|
|
60
|
-
* `specIds`: the requirement ids spec.md declares — only those count as spec citations.
|
|
61
|
-
*/
|
|
62
|
-
export function scanViewpointText(text: string, specIds: ReadonlySet<string> = new Set()): VpRow[] {
|
|
63
|
-
const rows: VpRow[] = [];
|
|
64
|
-
for (const r of tableRows(text)) {
|
|
65
|
-
const id = r.cells[0];
|
|
66
|
-
if (!VP_ID.test(id)) continue;
|
|
67
|
-
const exp = r.header?.findIndex((c) => EXPECTED_COL.test(c)) ?? -1;
|
|
68
|
-
const src = r.header?.findIndex((c) => SOURCE_COL.test(c)) ?? -1;
|
|
69
|
-
const cites = src > 0 ? r.cells[src] ?? '' : r.cells.slice(1).join(' | ');
|
|
70
|
-
rows.push({
|
|
71
|
-
id,
|
|
72
|
-
line: r.line,
|
|
73
|
-
refs: [...extractRefs(cites), ...[...cites.matchAll(SPEC_ID)].map((m) => m[0].toUpperCase()).filter((id) => specIds.has(id))],
|
|
74
|
-
manual: MANUAL.test(r.raw) || MANUAL.test(r.section),
|
|
75
|
-
// Without a Kỳ vọng header: any later cell with text that is neither a priority word nor a ref.
|
|
76
|
-
hasExpected: exp > 0
|
|
77
|
-
? !BLANK.test(r.cells[exp] ?? '')
|
|
78
|
-
: r.cells.slice(2).some((c) => !BLANK.test(c) && !PRIORITY.test(c) && extractRefs(c).length === 0),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return rows;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Scan one unit directory (`qa/screens/<n>`, `qa/flows/<n>`, …); `unit` names its feature files. */
|
|
85
|
-
export function scanViewpointUnit(unitDir: string, unit: string): ViewpointScan {
|
|
86
|
-
const reqDir = path.join(unitDir, 'requirements');
|
|
87
|
-
const vpPath = path.join(reqDir, 'test-viewpoint.md');
|
|
88
|
-
const vpText = readViewpointText(vpPath);
|
|
89
|
-
const universes = itemUniverses(reqDir);
|
|
90
|
-
const vpRows = scanViewpointText(vpText, new Set(universes.specIds.map((i) => i.ref)));
|
|
91
|
-
const { mode, reason } = viewpointMode(fs.existsSync(vpPath) ? readTextFile(vpPath) : null, vpRows);
|
|
92
|
-
const cited = new Set(vpRows.flatMap((r) => r.refs));
|
|
93
|
-
const { source, items } = citedUniverse(universes, cited, /(?<![\w-])UI-\d{1,3}\b/i.test(vpText));
|
|
94
|
-
const features = featureFilesFor(unitDir, unit).filter((f) => fs.existsSync(f));
|
|
95
|
-
// Same rule as the ledger (viewpoint-ledger.ts): an id is carried when the feature text contains it.
|
|
96
|
-
const featureText = features.map((f) => readTextFile(f).toLowerCase()).join('\n');
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
mode,
|
|
100
|
-
modeReason: reason,
|
|
101
|
-
vpRows,
|
|
102
|
-
distinctIds: new Set(vpRows.map((r) => r.id)).size,
|
|
103
|
-
rowsWithoutExpected: vpRows.filter((r) => !r.hasExpected).map((r) => r.id),
|
|
104
|
-
manualRows: vpRows.filter((r) => r.manual).map((r) => r.id),
|
|
105
|
-
itemSource: source,
|
|
106
|
-
uncitedItems: items.filter((i) => !cited.has(i.ref)),
|
|
107
|
-
withoutScenario: features.length === 0 ? null : vpRows.filter((r) => !featureText.includes(r.id.toLowerCase())).map((r) => r.id),
|
|
108
|
-
note: vpRows.length > 0 ? null
|
|
109
|
-
: fs.existsSync(vpPath) ? NO_VP_IDS_NOTE : 'requirements/test-viewpoint.md is missing',
|
|
110
|
-
};
|
|
111
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Viewpoint review checklist
|
|
2
|
-
|
|
3
|
-
create-test runs this review of `requirements/test-viewpoint.md` inline, in both modes, and asks
|
|
4
|
-
no question. Inputs: the viewpoint; the scan JSON (`sungen viewpoint scan --screen <name> --json`,
|
|
5
|
-
a flow takes `--flow <name>`); `spec.md`, `ui-inventory.md` and the message table when present.
|
|
6
|
-
Output: `requirements/viewpoint-review.md` (format below).
|
|
7
|
-
|
|
8
|
-
## Rules
|
|
9
|
-
- **Mode 1 — QA file (no `drafted-by: ai`): read only.** Never edit `test-viewpoint.md`. Every gap
|
|
10
|
-
goes into the review file; nothing is filled in. Look for what is **missing or contradicts the
|
|
11
|
-
spec**, not for how the QA writes: run only the checks the `Mode 1` column allows, as narrowed
|
|
12
|
-
there. A value the spec does not mention is not "invented". Wording the QA checks by eye ("Hiển thị đúng", "Khớp design"), their question style and
|
|
13
|
-
their environment marks are the QA's convention, never a finding.
|
|
14
|
-
- **Mode 2 — `drafted-by: ai`: review your own draft.** Run the checks, fix the draft, run them
|
|
15
|
-
again — checks 4 and 9 field by field, before the review is written. The review file keeps only
|
|
16
|
-
what needs a QA decision: questions, inferred expectations, scope calls.
|
|
17
|
-
- **Cite or drop.** Every finding names at least one VP id from the scan's `vpRows`, or an item ref
|
|
18
|
-
(`item N`, a spec id). A gap with no VP yet cites the item or spec line it comes from. A finding
|
|
19
|
-
you cannot anchor is not written.
|
|
20
|
-
- **Distinct ids.** The scan lists a row per table occurrence: an id repeated in the questions table
|
|
21
|
-
(mục 5) is a reference, not a second VP. Count and cite each id once.
|
|
22
|
-
- **No VP ids** (scan `note`: file has no VP ids): cite the file's own row labels (`DT-2 case 3`, a
|
|
23
|
-
field name) with the line number, plus item refs.
|
|
24
|
-
- **Meaning, not format.** Headings, column order, wording style and language are never findings.
|
|
25
|
-
- The scan's `withoutScenario` is generation work (a scenario per VP id), not a review finding.
|
|
26
|
-
- **Audit findings** (create-test may route one here, check `audit`) enter only when they cite a VP id
|
|
27
|
-
from the scan's `vpRows` and the fix is an edit the QA can make in this file. A finding about
|
|
28
|
-
sungen itself — the page type, an id it read in a message code or a section-8 table — stays in the
|
|
29
|
-
hand-back, never in the review.
|
|
30
|
-
|
|
31
|
-
## Checks
|
|
32
|
-
One check, one fix hint. **Output order: 2, 9, 3, 6, 1, 4, 5, 7, 8** — highest impact first.
|
|
33
|
-
|
|
34
|
-
| # | Check | Mode 1 | Read | Suggestion to write |
|
|
35
|
-
|---|-------|--------|------|---------------------|
|
|
36
|
-
| 1 | Each VP row says what to test and what to expect: `Kỳ vọng` is not empty and not vague ("OK", "đúng", "hiển thị đúng", "như spec"). | empty `Kỳ vọng` only | scan `rowsWithoutExpected`, the rows | Name the observable: message code, target screen, control state, value. |
|
|
37
|
-
| 2 | Every expectation is traceable to the spec, the UI, the message table, a Design Decision (mục 6) or a Shared Check of this skill. One found in none of them is invented, unless mục 5 holds its question (`Nguồn: Suy luận → Q-0x`). | only an expectation that contradicts what the spec states; a value the spec does not mention (a URL, a screen id, a rule of thumb) is the QA's knowledge, not a finding | spec, UI, message table, mục 6 | Cite the source, or turn the expectation into a closed question in mục 5. |
|
|
38
|
-
| 3 | A rule the spec applies to several fields names every field (`Ô` column, or one row per field). | ✓ | spec rules | Name the missing fields. |
|
|
39
|
-
| 4 | Each accepted-input class in the spec (whitelist, value list, allowed characters, conversion such as trim, case, full→half width) has a positive VP — also a whitelist or list the spec names but does not reproduce. | ✓ | spec rules | Add an "accepted, no error" VP for the class, citing the list by name; not a question in mục 5. |
|
|
40
|
-
| 5 | A VP that needs a real device, account, external service or elapsed time is marked `# MANUAL/ENV`. | — | scan `manualRows`, the rows | Mark the row or its group heading. |
|
|
41
|
-
| 6 | Each entry of the scan's `uncitedItems`: deliberately out of scope (mục 6 or the scope note says so), or a gap? | ✓ | scan `uncitedItems`, mục 6, mục 7 | Add a VP citing `item N`, or a Design Decision saying why it is out. |
|
|
42
|
-
| 7 | Questions are closed (yes/no, or pick one of the listed options) and carry `Major` / `Minor`. | — | mục 5 | Rewrite as a closed question; add the level. |
|
|
43
|
-
| 8 | An edge case (mục 4) or decision (mục 6) states testable behaviour that no VP id carries — alone or together: an edge case split across several rows is carried. | ✓ | mục 4, mục 6 | Add a VP row. If a row already covers it, name that id in this finding — not in mục 4, where an id counts as a second declaration (`VIEWPOINT-ID-REUSED`). |
|
|
44
|
-
| 9 | A row's `Kỳ vọng` is only a question pointer (`→ Q-0x`) although the spec states the expected result for that condition. | ✓ | spec, mục 5 | Write the spec's expectation in the row; keep the question for the part the spec leaves open. |
|
|
45
|
-
|
|
46
|
-
## Output — `requirements/viewpoint-review.md`
|
|
47
|
-
Overwrite it on every run, never append. Write Problem and Suggestion in the viewpoint's language;
|
|
48
|
-
one row per finding, or one row listing every id that shares the same fix.
|
|
49
|
-
|
|
50
|
-
```markdown
|
|
51
|
-
# Viewpoint review — <unit>
|
|
52
|
-
> Date: <YYYY-MM-DD> · test-viewpoint.md sha256: <hash> · Mode: <1 — QA file, review only | 2 — AI draft>
|
|
53
|
-
> QA: tick `[x]` once handled, or delete the row. `sungen audit` reports the rows still open.
|
|
54
|
-
|
|
55
|
-
| # | VP / item | Check | Problem | Suggestion | [ ] |
|
|
56
|
-
|---|-----------|-------|---------|------------|-----|
|
|
57
|
-
| 1 | VP-…-NNN · item N | 2 | <what is wrong, in one sentence> | <the fix> | [ ] |
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
- `sha256` of `test-viewpoint.md` as reviewed (`shasum -a 256` / `sha256sum`); mode 2: after your fixes.
|
|
61
|
-
- At most 20 rows, in check order. Past 20, one line under the table: `+N more (checks …)`.
|
|
62
|
-
- Nothing to report: keep the header, write `No open points.` instead of the table.
|
|
63
|
-
- The hand-back counts the rows: `Viewpoint review: N điểm cần QA xem`.
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# Test Viewpoint — <MÃ MÀN> <Tên màn>
|
|
2
|
-
<!--
|
|
3
|
-
Template Test Viewpoint (skill sungen-viewpoint). Dòng mẫu và hướng dẫn nằm trong comment, nên khung chưa điền khai báo 0 viewpoint. Điền: chép dòng mẫu ra ngoài comment rồi sửa; nhóm không áp dụng thì xoá, ghi lý do ở bảng Coverage (mục 7).
|
|
4
|
-
Nháp do AI soạn (unit chưa có viewpoint QA): dòng ĐẦU TIÊN của file là `drafted-by: ai`, đặt ngoài comment. Nháp không bao giờ được lấy làm chuẩn đo; QA duyệt xong thì accept (`sungen audit … --accept-viewpoint`), accept xoá dòng đó và từ đó file thuộc QA. `sungen add` / `sungen add-flow` không ghi dòng này.
|
|
5
|
-
-->
|
|
6
|
-
> **Tài liệu đầu vào:** <đặc tả / design / bảng message của màn>
|
|
7
|
-
> **Loại tài liệu:** bảng quan điểm kiểm thử — KHÔNG phải test case chi tiết. Mỗi quan điểm gắn mã `VP-…` để truy vết sang test case.
|
|
8
|
-
> **Nguyên tắc:** mỗi dòng trả lời câu hỏi "test CÁI GÌ" và "kỳ vọng ra sao"; điểm spec chưa rõ thì ghi ở mục 5, **không tự bịa kỳ vọng** — spec đã nói thì ghi kỳ vọng theo spec, chỉ phần spec bỏ ngỏ mới thành câu hỏi. Quan điểm cần môi trường/thiết bị/thời gian thật: đánh dấu `# MANUAL/ENV`.
|
|
9
|
-
|
|
10
|
-
## 0. Metadata
|
|
11
|
-
| Trường | Giá trị |
|
|
12
|
-
|--------|---------|
|
|
13
|
-
| Màn hình | <mã + tên gốc / tên tiếng Việt, route> |
|
|
14
|
-
| Vị trí trong flow | <vào từ đâu, đi tiếp tới đâu> |
|
|
15
|
-
| Primary UI pattern | <form / list / detail / create / login / search…> |
|
|
16
|
-
| Nền tảng | <trình duyệt / thiết bị / độ phân giải cần test> |
|
|
17
|
-
| Đặc thù cần nhấn | <rủi ro hoặc đặc điểm quan trọng nhất của màn> |
|
|
18
|
-
| Tiền điều kiện | <đăng nhập? role? dữ liệu cần chuẩn bị?> |
|
|
19
|
-
| Ngày tạo viewpoint | <YYYY-MM-DD> |
|
|
20
|
-
|
|
21
|
-
## 1. UI Patterns Identified
|
|
22
|
-
*(Nhận diện màn dùng pattern nào → quyết định nhóm viewpoint nào cần áp dụng.)*
|
|
23
|
-
|
|
24
|
-
| # | Pattern | Áp dụng? | Ghi chú |
|
|
25
|
-
|---|---------|----------|---------|
|
|
26
|
-
<!-- Áp dụng? = ✅ / ❌ (❌ thì ghi lý do). Một dòng cho mỗi pattern, số + tên theo catalog sungen-viewpoint (web 1–17, mobile thêm 18–32):
|
|
27
|
-
| 1 | Form & Inputs | ✅ | <các ô nhập, validate lúc nào> |
|
|
28
|
-
| 12 | Modal / Dialog | ❌ | <lý do không áp dụng> |
|
|
29
|
-
-->
|
|
30
|
-
|
|
31
|
-
## 2. Priority Viewpoints
|
|
32
|
-
### Viewpoint grouping — ưu tiên theo nhóm VP
|
|
33
|
-
| Nhóm VP | Priority | Lý do |
|
|
34
|
-
|---------|----------|-------|
|
|
35
|
-
<!-- Priority = High / Med / Low; một dòng cho mỗi nhóm có ở mục 3: VP-VAL (Validation) · VP-LOGIC (Nghiệp vụ) · VP-UI-BTN (Nút & trạng thái) · VP-NAV (Điều hướng) · VP-AUTH (Quyền / tiền điều kiện) · VP-SEC (Bảo mật) · VP-UI (Hiển thị). Ví dụ:
|
|
36
|
-
| VP-VAL (Validation) | High | <lý do> | -->
|
|
37
|
-
|
|
38
|
-
## 3. Viewpoints chi tiết
|
|
39
|
-
*(Pri = priority. Nguồn = số item trong spec/UI. Mỗi dòng = "test cái gì".)*
|
|
40
|
-
<!--
|
|
41
|
-
- Mọi bảng mục 3 dùng cùng cột. Mã VP-<NHÓM>[-<FIELD>]-NNN ở cột đầu, mỗi mã khai báo một lần. Flow: mã FL-…, thêm nhóm liên màn (dữ liệu / quyền giữ qua các màn).
|
|
42
|
-
- Kỳ vọng = thứ nhìn thấy được: mã message, màn đích, trạng thái control. Kỳ vọng trống thì dòng KHÔNG tính là viewpoint.
|
|
43
|
-
- Ô: để trống khi cả nhóm là một ô. Một luật áp cho nhiều ô: liệt kê đủ từng ô, không lấy 1 ô làm đại diện.
|
|
44
|
-
- Pri = High / Med / Low. Nguồn = `item N` · `SPEC <id>` · `Suy luận → Q-0x` (suy luận bắt buộc có câu hỏi ở mục 5). Cần thiết bị / tài khoản / thời gian thật: `# MANUAL/ENV` trong ô Quan điểm hoặc tiêu đề nhóm.
|
|
45
|
-
-->
|
|
46
|
-
|
|
47
|
-
### A. VP-UI — Hiển thị tĩnh & trạng thái ban đầu
|
|
48
|
-
| VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
|
|
49
|
-
|----|--------------------|---|---------|-----|-------|
|
|
50
|
-
<!-- | VP-UI-001 | Trạng thái lúc mở màn | | <nhãn, placeholder, giá trị mặc định, nút chính disabled / enabled> | | item N | -->
|
|
51
|
-
|
|
52
|
-
### B. VP-VAL-<FIELD> — <Tên field> (item x)
|
|
53
|
-
| VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
|
|
54
|
-
|----|--------------------|---|---------|-----|-------|
|
|
55
|
-
<!--
|
|
56
|
-
| VP-VAL-…-001 | Rỗng → blur / submit | | → <mã message> | High | |
|
|
57
|
-
| VP-VAL-…-002 | Chỉ khoảng trắng / khoảng trắng đầu-cuối | | | | |
|
|
58
|
-
| VP-VAL-…-003 | Biên độ dài: min−1 / min / max / max+1 | | | High | |
|
|
59
|
-
| VP-VAL-…-004 | Sai định dạng / ký tự không cho phép | | | | |
|
|
60
|
-
| VP-VAL-…-008 | Mọi ký tự / giá trị spec cho phép (whitelist, danh sách, chuyển đổi) được chấp nhận | | Không lỗi, giá trị giữ nguyên / đã chuyển đổi | High | |
|
|
61
|
-
| VP-VAL-…-005 | Full-width / half-width | | | | |
|
|
62
|
-
| VP-VAL-…-006 | Paste / autofill | | | | |
|
|
63
|
-
| VP-VAL-…-007 | Đang lỗi → sửa đúng → message biến mất | | | | |
|
|
64
|
-
-->
|
|
65
|
-
*(Lặp nhóm B cho từng field.)*
|
|
66
|
-
|
|
67
|
-
### C. VP-LOGIC — Nghiệp vụ chính / submit
|
|
68
|
-
| VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
|
|
69
|
-
|----|--------------------|---|---------|-----|-------|
|
|
70
|
-
<!--
|
|
71
|
-
| VP-LOGIC-001 | Happy path | | | High | |
|
|
72
|
-
| VP-LOGIC-002 | Server trả lỗi nghiệp vụ | | | | |
|
|
73
|
-
| VP-LOGIC-003 | Lỗi mạng / server `# MANUAL/ENV` | | | | |
|
|
74
|
-
-->
|
|
75
|
-
|
|
76
|
-
### D. VP-UI-BTN — Nút & trạng thái
|
|
77
|
-
| VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
|
|
78
|
-
|----|--------------------|---|---------|-----|-------|
|
|
79
|
-
<!--
|
|
80
|
-
| VP-UI-BTN-001 | Chưa đủ điều kiện → disabled | | | High | |
|
|
81
|
-
| VP-UI-BTN-002 | Đủ điều kiện → enabled | | | High | |
|
|
82
|
-
| VP-UI-BTN-003 | Loading sau khi bấm | | | | |
|
|
83
|
-
| VP-UI-BTN-004 | Bấm kép → chỉ 1 request `# MANUAL/ENV` | | | High | |
|
|
84
|
-
-->
|
|
85
|
-
|
|
86
|
-
### E. VP-NAV — Điều hướng
|
|
87
|
-
| VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
|
|
88
|
-
|----|--------------------|---|---------|-----|-------|
|
|
89
|
-
<!-- | VP-NAV-001 | | | → <màn đích>, cùng tab / tab mới | | | -->
|
|
90
|
-
|
|
91
|
-
### F. VP-AUTH — Quyền / tiền điều kiện truy cập (nếu có)
|
|
92
|
-
### G. VP-SEC — Bảo mật (nếu có)
|
|
93
|
-
### H. VP-UI-PLTF — Nền tảng / thiết bị (nếu có)
|
|
94
|
-
|
|
95
|
-
### DT-1 — Bảng quyết định: <luật phụ thuộc ≥ 2 điều kiện> (nếu có)
|
|
96
|
-
<!-- Mỗi Case = 1 dòng có mã VP. Cột giữa là điều kiện (✓ / ✗ / giá trị), rồi Hành động và Kỳ vọng. Ví dụ:
|
|
97
|
-
| VP | <Điều kiện 1> | <Điều kiện 2> | Hành động | Kỳ vọng | Pri | Nguồn |
|
|
98
|
-
|----|---------------|---------------|-----------|---------|-----|-------|
|
|
99
|
-
| VP-LOGIC-010 | ✓ | ✗ | Tick nốt ô con còn lại | "Chọn tất cả" tự → ✓ | High | item N |
|
|
100
|
-
-->
|
|
101
|
-
|
|
102
|
-
## 4. Edge Cases
|
|
103
|
-
*(Ca dễ bị sót khi viết test case.)*
|
|
104
|
-
<!--
|
|
105
|
-
- <biên nối tiếp: max rồi max+1>
|
|
106
|
-
- <bấm nút đúng lúc field vừa thành không hợp lệ>
|
|
107
|
-
- <mất mạng trước khi bấm vs trong lúc chờ phản hồi>
|
|
108
|
-
- <phiên hết hạn giữa chừng>
|
|
109
|
-
-->
|
|
110
|
-
|
|
111
|
-
## 5. Open questions / Điểm CHƯA CHỐT
|
|
112
|
-
*(Spec chưa nêu → KHÔNG tự bịa kỳ vọng; ghi thành câu hỏi đóng để hỏi BA/dev.)*
|
|
113
|
-
|
|
114
|
-
| Mã | Mức | VP liên quan | Câu hỏi đóng cần BA/dev xác nhận |
|
|
115
|
-
|-----|-----|--------------|----------------------------------|
|
|
116
|
-
<!-- Mức: Major = test case của VP liên quan chờ câu trả lời; Minor = không chặn. -->
|
|
117
|
-
<!-- | Q-01 | Major / Minor | VP-… | <câu hỏi cụ thể, trả lời được bằng có/không hoặc chọn 1 phương án> | -->
|
|
118
|
-
|
|
119
|
-
## Known issues — lỗi đã biết (KHÔNG phải câu hỏi)
|
|
120
|
-
*(Lỗi đã xác nhận, còn tồn tại: scenario của VP đó gắn `@known-defect:DEF-NNN` (bản ghi ở `qa/defects/`), đỏ có chủ đích. Spec chưa rõ thì ghi ở mục 5.)*
|
|
121
|
-
|
|
122
|
-
| KI | VP liên quan | Lỗi | Hiện tượng |
|
|
123
|
-
|----|--------------|-----|------------|
|
|
124
|
-
<!-- | KI-1 | VP-… | DEF-001 | <thấy gì, khác Kỳ vọng ở đâu> | -->
|
|
125
|
-
|
|
126
|
-
## 6. Design Decisions
|
|
127
|
-
*(Quyết định có chủ đích của spec — KHÔNG báo "thiếu" hoặc "bug" cho các mục này.)*
|
|
128
|
-
<!--
|
|
129
|
-
- D1: <vd: nút disabled tới khi đủ điều kiện> (SPEC <id>)
|
|
130
|
-
- D2: <vd: lỗi đăng nhập không nói rõ sai email hay sai mật khẩu>
|
|
131
|
-
-->
|
|
132
|
-
|
|
133
|
-
## 7. Traceability & tự nhận giới hạn
|
|
134
|
-
- **Tổng: N viewpoint** — liệt kê số lượng theo từng nhóm. Mọi item trong spec có ít nhất 1 VP trỏ về.
|
|
135
|
-
- ⚠ **Giới hạn:** bảng chứng minh "mọi VP đã liệt kê có kỳ vọng" — KHÔNG chứng minh "đã liệt kê ĐỦ VP". Đối chiếu lại với đặc tả gốc + bảng message trước khi viết test case.
|
|
136
|
-
- **Scope note:** <VP nào cần môi trường/thiết bị thật, VP nào test ở tầng API/E2E>.
|
|
137
|
-
- Còn **K điểm chưa chốt** (Q-…) — test case cho VP liên quan chờ câu trả lời.
|
|
138
|
-
|
|
139
|
-
### Coverage — độ phủ tự kiểm
|
|
140
|
-
<!-- Mỗi nhóm một dòng. Trạng thái: `có VP` (✓) · `để sau → <đích>` (# MANUAL/ENV, unit/flow khác, Q-0x) · `Không áp dụng` (➖) kèm lý do.
|
|
141
|
-
Thêm dòng nền tảng theo catalog sungen-viewpoint khi màn có: web — XSS / SQL injection / URL (Shared Checks); mobile — Bàn phím ảo (30) · Vòng đời app & xoay màn (32) · Offline (27) · Quyền hệ thống (22) · Deep-link (26). -->
|
|
142
|
-
| Nhóm kiểm tra | Trạng thái | VP / lý do |
|
|
143
|
-
|---------------|------------|------------|
|
|
144
|
-
| Happy path | | |
|
|
145
|
-
| Negative | | |
|
|
146
|
-
| Biên | | |
|
|
147
|
-
| Charset | | |
|
|
148
|
-
| Trạng thái nút | | |
|
|
149
|
-
| Điều hướng | | |
|
|
150
|
-
| Quyền | | |
|
|
151
|
-
| Bảo mật | | |
|
|
152
|
-
| Nền tảng | | |
|
|
153
|
-
| Trạng thái tải / rỗng / lỗi | | |
|
|
154
|
-
|
|
155
|
-
## 8. References · Phụ lục — Mã thông báo dùng trong tài liệu
|
|
156
|
-
| Mã | Nghĩa | Kiểu hiển thị |
|
|
157
|
-
|----|-------|---------------|
|
|
158
|
-
<!-- | M01 | <nội dung message> | <dưới ô nhập / trên form / toast / mail> | -->
|