@sun-asterisk/sungen 3.2.33 → 3.2.34-beta.1
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 +4 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +54 -10
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +5 -1
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts.map +1 -1
- package/dist/cli/commands/changed.js +2 -3
- package/dist/cli/commands/changed.js.map +1 -1
- package/dist/cli/commands/dashboard.d.ts.map +1 -1
- package/dist/cli/commands/dashboard.js +2 -28
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/defect.d.ts +7 -0
- package/dist/cli/commands/defect.d.ts.map +1 -0
- package/dist/cli/commands/defect.js +68 -0
- package/dist/cli/commands/defect.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +75 -42
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +9 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +7 -0
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/select.d.ts +12 -0
- package/dist/cli/commands/select.d.ts.map +1 -0
- package/dist/cli/commands/select.js +122 -0
- package/dist/cli/commands/select.js.map +1 -0
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +98 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +3 -0
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/viewpoint.d.ts +7 -0
- package/dist/cli/commands/viewpoint.d.ts.map +1 -0
- package/dist/cli/commands/viewpoint.js +70 -0
- package/dist/cli/commands/viewpoint.js.map +1 -0
- package/dist/cli/index.js +13 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/dashboard/types.d.ts +1 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.d.ts +4 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +12 -11
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +8 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +12 -0
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +7 -0
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/manual-hybrid-oracle.d.ts +54 -0
- package/dist/exporters/manual-hybrid-oracle.d.ts.map +1 -0
- package/dist/exporters/manual-hybrid-oracle.js +114 -0
- package/dist/exporters/manual-hybrid-oracle.js.map +1 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +55 -12
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +19 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.js +76 -6
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +8 -2
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +15 -3
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.js +6 -0
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/render-csv.js +1 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -1
- package/dist/exporters/matrix/render-testcase-sheet.js +10 -4
- package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +3 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +13 -1
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/matrix/wording.d.ts +1 -1
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +10 -1
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +1 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/run-environment.d.ts +32 -0
- package/dist/exporters/run-environment.d.ts.map +1 -0
- package/dist/exporters/run-environment.js +125 -0
- package/dist/exporters/run-environment.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +18 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +49 -3
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts +0 -4
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +0 -7
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +5 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +11 -4
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +49 -19
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +4 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +23 -2
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +3 -1
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +3 -1
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/harness/audit.d.ts +3 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +48 -15
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/counted-assertions.d.ts +2 -0
- package/dist/harness/counted-assertions.d.ts.map +1 -0
- package/dist/harness/counted-assertions.js +38 -0
- package/dist/harness/counted-assertions.js.map +1 -0
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +69 -27
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/defect-draft.d.ts +14 -0
- package/dist/harness/defect-draft.d.ts.map +1 -0
- package/dist/harness/defect-draft.js +143 -0
- package/dist/harness/defect-draft.js.map +1 -0
- package/dist/harness/defects.d.ts +24 -0
- package/dist/harness/defects.d.ts.map +1 -0
- package/dist/harness/defects.js +118 -0
- package/dist/harness/defects.js.map +1 -0
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +2 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/next-step.d.ts +2 -4
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +50 -5
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +45 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +142 -24
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +6 -4
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/read-text.d.ts +15 -0
- package/dist/harness/read-text.d.ts.map +1 -1
- package/dist/harness/read-text.js +25 -0
- package/dist/harness/read-text.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +7 -1
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/select-by-ids.d.ts +31 -0
- package/dist/harness/select-by-ids.d.ts.map +1 -0
- package/dist/harness/select-by-ids.js +106 -0
- package/dist/harness/select-by-ids.js.map +1 -0
- package/dist/harness/sensors.d.ts +8 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +92 -21
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-branches.js +1 -1
- package/dist/harness/spec-branches.js.map +1 -1
- package/dist/harness/triage-signals.d.ts +20 -0
- package/dist/harness/triage-signals.d.ts.map +1 -0
- package/dist/harness/triage-signals.js +30 -0
- package/dist/harness/triage-signals.js.map +1 -0
- package/dist/harness/triage-unit.d.ts +23 -0
- package/dist/harness/triage-unit.d.ts.map +1 -0
- package/dist/harness/triage-unit.js +142 -0
- package/dist/harness/triage-unit.js.map +1 -0
- package/dist/harness/triage.d.ts +28 -0
- package/dist/harness/triage.d.ts.map +1 -0
- package/dist/harness/triage.js +99 -0
- package/dist/harness/triage.js.map +1 -0
- package/dist/harness/ui-inventory.d.ts +40 -0
- package/dist/harness/ui-inventory.d.ts.map +1 -0
- package/dist/harness/ui-inventory.js +191 -0
- package/dist/harness/ui-inventory.js.map +1 -0
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +7 -1
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-draft-header.d.ts +7 -0
- package/dist/harness/viewpoint-draft-header.d.ts.map +1 -0
- package/dist/harness/viewpoint-draft-header.js +100 -0
- package/dist/harness/viewpoint-draft-header.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts +13 -3
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +104 -18
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/harness/viewpoint-mode.d.ts +9 -0
- package/dist/harness/viewpoint-mode.d.ts.map +1 -0
- package/dist/harness/viewpoint-mode.js +52 -0
- package/dist/harness/viewpoint-mode.js.map +1 -0
- package/dist/harness/viewpoint-priority.d.ts +19 -0
- package/dist/harness/viewpoint-priority.d.ts.map +1 -0
- package/dist/harness/viewpoint-priority.js +36 -0
- package/dist/harness/viewpoint-priority.js.map +1 -0
- package/dist/harness/viewpoint-review-open.d.ts +6 -0
- package/dist/harness/viewpoint-review-open.d.ts.map +1 -0
- package/dist/harness/viewpoint-review-open.js +90 -0
- package/dist/harness/viewpoint-review-open.js.map +1 -0
- package/dist/harness/viewpoint-scan-refs.d.ts +50 -0
- package/dist/harness/viewpoint-scan-refs.d.ts.map +1 -0
- package/dist/harness/viewpoint-scan-refs.js +200 -0
- package/dist/harness/viewpoint-scan-refs.js.map +1 -0
- package/dist/harness/viewpoint-scan.d.ts +41 -0
- package/dist/harness/viewpoint-scan.d.ts.map +1 -0
- package/dist/harness/viewpoint-scan.js +118 -0
- package/dist/harness/viewpoint-scan.js.map +1 -0
- package/dist/orchestrator/flow-manager.d.ts +0 -1
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +3 -52
- 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 +8 -62
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/agents/discovery.md +11 -3
- package/dist/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +32 -24
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +78 -15
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +22 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +18 -9
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
- package/dist/orchestrator/templates/playwright.config.ts +4 -2
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +24 -2
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +23 -2
- package/dist/orchestrator/trace-mode-install.d.ts +14 -0
- package/dist/orchestrator/trace-mode-install.d.ts.map +1 -0
- package/dist/orchestrator/trace-mode-install.js +132 -0
- package/dist/orchestrator/trace-mode-install.js.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +23 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js +48 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
- package/dist/orchestrator/viewpoint-scaffold.d.ts +3 -0
- package/dist/orchestrator/viewpoint-scaffold.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-scaffold.js +70 -0
- package/dist/orchestrator/viewpoint-scaffold.js.map +1 -0
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +49 -10
- package/src/cli/commands/capability.ts +3 -1
- package/src/cli/commands/changed.ts +2 -4
- package/src/cli/commands/dashboard.ts +1 -26
- package/src/cli/commands/defect.ts +49 -0
- package/src/cli/commands/delivery.ts +70 -42
- package/src/cli/commands/results-merge.ts +7 -1
- package/src/cli/commands/select.ts +88 -0
- package/src/cli/commands/triage.ts +57 -0
- package/src/cli/commands/update.ts +3 -0
- package/src/cli/commands/viewpoint.ts +58 -0
- package/src/cli/index.ts +13 -2
- package/src/dashboard/templates/index.html +1 -1
- package/src/dashboard/types.ts +1 -0
- package/src/exporters/csv-exporter.ts +12 -11
- package/src/exporters/feature-parser.ts +12 -0
- package/src/exporters/json-exporter.ts +6 -1
- package/src/exporters/manual-hybrid-oracle.ts +112 -0
- package/src/exporters/matrix/build.ts +57 -11
- package/src/exporters/matrix/carry-manual-results.ts +70 -7
- package/src/exporters/matrix/export.ts +16 -2
- package/src/exporters/matrix/gates.ts +6 -0
- package/src/exporters/matrix/render-csv.ts +1 -1
- package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
- package/src/exporters/matrix/render-xlsx.ts +3 -0
- package/src/exporters/matrix/types.ts +13 -1
- package/src/exporters/matrix/wording.ts +10 -1
- package/src/exporters/playwright-report-parser.ts +1 -0
- package/src/exporters/run-environment.ts +89 -0
- package/src/exporters/scenario-merger.ts +51 -5
- package/src/exporters/test-data-resolver.ts +0 -6
- package/src/exporters/types.ts +5 -0
- package/src/generators/gherkin-parser/index.ts +50 -18
- package/src/generators/test-generator/adapters/adapter-interface.ts +4 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
- package/src/generators/test-generator/code-generator.ts +23 -2
- package/src/generators/test-generator/diagnostics.ts +3 -1
- package/src/harness/audit.ts +52 -17
- package/src/harness/counted-assertions.ts +33 -0
- package/src/harness/data-driven-lint.ts +82 -24
- package/src/harness/defect-draft.ts +115 -0
- package/src/harness/defects.ts +94 -0
- package/src/harness/manifest.ts +2 -2
- package/src/harness/next-step.ts +47 -5
- package/src/harness/parse.ts +151 -27
- package/src/harness/quality-gates.ts +6 -4
- package/src/harness/read-text.ts +25 -0
- package/src/harness/script-check.ts +7 -1
- package/src/harness/select-by-ids.ts +83 -0
- package/src/harness/sensors.ts +91 -21
- package/src/harness/spec-branches.ts +1 -1
- package/src/harness/triage-signals.ts +46 -0
- package/src/harness/triage-unit.ts +113 -0
- package/src/harness/triage.ts +127 -0
- package/src/harness/ui-inventory.ts +164 -0
- package/src/harness/viewpoint-baseline.ts +7 -1
- package/src/harness/viewpoint-draft-header.ts +55 -0
- package/src/harness/viewpoint-ledger.ts +111 -24
- package/src/harness/viewpoint-mode.ts +49 -0
- package/src/harness/viewpoint-priority.ts +38 -0
- package/src/harness/viewpoint-review-open.ts +51 -0
- package/src/harness/viewpoint-scan-refs.ts +154 -0
- package/src/harness/viewpoint-scan.ts +111 -0
- package/src/orchestrator/flow-manager.ts +3 -53
- package/src/orchestrator/screen-manager.ts +7 -61
- package/src/orchestrator/templates/ai-src/agents/discovery.md +11 -3
- package/src/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +32 -24
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +78 -15
- package/src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +22 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +18 -9
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
- package/src/orchestrator/templates/playwright.config.ts +4 -2
- package/src/orchestrator/templates/specs-url-assert.ts +23 -2
- package/src/orchestrator/trace-mode-install.ts +94 -0
- package/src/orchestrator/viewpoint-pattern-catalog.ts +61 -0
- package/src/orchestrator/viewpoint-scaffold.ts +36 -0
|
@@ -121,7 +121,7 @@ use chrome, FireFox or Internet Explorer 11`)}var a=e(`safe-buffer`),o=e(`random
|
|
|
121
121
|
(Test evidence, DefectID, Actual result)`]));let f=[`Accessing`,`GUI`,`Function`],p=new Map;for(let e of o){let t=p.get(e.category2)||[];t.push(e),p.set(e.category2,t)}let m=new Set,h=(e,t)=>{d.push(UN([``,e,``,``,``,``,``,``,``,``,``,``,``,``,``,``]));for(let e of t)d.push(UN(LN(e)))};for(let e of f){let t=p.get(e);!t||t.length===0||(m.add(e),h(e,t))}for(let[e,t]of p.entries())m.has(e)||h(e,t);return``+d.join(`
|
|
122
122
|
`)+`
|
|
123
123
|
`}async function MN(e,t){let n=IN(e,t.screens),r=VN();for(let t of n)for(let n of ON(t)){let t=DN(FN(n),{sungenVersion:e.environment.sungenVersion});n.apiDetail&&n.apiDetail.length>0&&EN(t,n.apiDetail);let i=await KN(await t.xlsx.writeBuffer()),a=new Blob([i.buffer],{type:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`}),o=URL.createObjectURL(a);GN(`${n.basename}-testcases_${r}.xlsx`,o),setTimeout(()=>URL.revokeObjectURL(o),1e3)}}function NN(e){let t={total:e.length,passed:0,failed:0,pending:0,na:0,notCompiled:0,passRate:0};for(let n of e)n.status===`Passed`?t.passed++:n.status===`Failed`?t.failed++:n.status===`Pending`?t.pending++:n.status===`N/A`&&t.na++,n.type===`Not compiled`&&t.notCompiled++;let n=t.passed+t.failed;return t.passRate=n>0?t.passed/n:0,t}function PN(e){return{tcId:e.tcId,category1:e.category1,category2:e.category2,precondition:e.precondition,testData:e.testData,steps:RN(e.steps),expectedResults:RN(e.expectedResults),priority:e.priority,testcaseType:e.type,testResult:e.status,executedDate:e.executedDate??``,testExecutor:e.testExecutor??``,testEnvironment:e.testEnvironment??``,note:zN(e)}}function FN(e){let t=e.specLink??``,n=(n,r)=>{let i=NN(r);return{sheetName:n,summary:{screen:e.basename,total:i.total,passed:i.passed,failed:i.failed,pending:i.pending,na:i.na},rows:r.map(PN),specLink:t}},r=[];if(e.locales&&e.locales.length>=2){let t=[];for(let i of e.locales){let e=i.scenarios.filter(e=>e.type!==`Manual`);r.push(n(`${i.displayCode} Auto`,e)),i.locale===``&&(t=i.scenarios.filter(e=>e.type===`Manual`))}t.length===0&&e.locales[0]&&(t=e.locales[0].scenarios.filter(e=>e.type===`Manual`)),r.push(n(`Manual`,t))}else{let t=e.scenarios.filter(e=>e.type!==`Manual`),i=e.scenarios.filter(e=>e.type===`Manual`);r.push(n(`Auto`,t)),r.push(n(`Manual`,i))}return r}function IN(e,t){if(!t||t.length===0)return e.screens;let n=new Set(t);return e.screens.filter(e=>n.has(e.name))}function LN(e){return[e.tcId,``,``,``,e.category1,e.precondition,e.testData,RN(e.steps),RN(e.expectedResults),e.priority,e.type,e.status,e.executedDate??``,e.testExecutor??``,e.testEnvironment??``,zN(e)]}function RN(e){return!e||e.length===0?``:e.length===1?e[0]:e.map((e,t)=>`${t+1}. ${e}`).join(`
|
|
124
|
-
`)}function zN(e){if(e.type===`Not compiled`)return`Scenario not compiled — re-run \`sungen generate --screen ${e.category4}\``;if(e.status!==`Failed`)return``;let t=[];if(e.errorMessage){let n=e.errorMessage.replace(/^Error:\s*/i,``),r=n.length>200?n.substring(0,197)+`…`:n;t.push(`• Error: ${r}`)}return e.tracePath&&t.push(`• Trace: ${e.tracePath}`),t.join(`
|
|
124
|
+
`)}function zN(e){if(e.type===`Not compiled`)return`Scenario not compiled — re-run \`sungen generate --screen ${e.category4}\``;if(e.type===`Manual`&&e.manualReason&&!e.executedDate)return`Manual: ${e.manualReason}`;if(e.status!==`Failed`)return``;let t=[];if(e.errorMessage){let n=e.errorMessage.replace(/^Error:\s*/i,``),r=n.length>200?n.substring(0,197)+`…`:n;t.push(`• Error: ${r}`)}return e.tracePath&&t.push(`• Trace: ${e.tracePath}`),t.join(`
|
|
125
125
|
`)}function BN(){let e=new Date(Date.now()+kN);return`${String(e.getUTCDate()).padStart(2,`0`)}/${String(e.getUTCMonth()+1).padStart(2,`0`)}/${e.getUTCFullYear()}`}function VN(){let e=new Date(Date.now()+kN);return`${e.getUTCFullYear()}-${String(e.getUTCMonth()+1).padStart(2,`0`)}-${String(e.getUTCDate()).padStart(2,`0`)}_${String(e.getUTCHours()).padStart(2,`0`)}-${String(e.getUTCMinutes()).padStart(2,`0`)}-${String(e.getUTCSeconds()).padStart(2,`0`)}`}function HN(e){if(e==null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"`+t.replace(/"/g,`""`)+`"`:t}function UN(e){return e.map(HN).join(`,`)}function WN(e,t,n){let r=new Blob([t],{type:n}),i=URL.createObjectURL(r);GN(e,i),setTimeout(()=>URL.revokeObjectURL(i),1e3)}function GN(e,t){let n=document.createElement(`a`);n.href=t,n.download=e,document.body.appendChild(n),n.click(),n.remove()}async function KN(e){let t=await aN.default.loadAsync(e),n=Object.keys(t.files).filter(e=>/^xl\/drawings\/drawing\d+\.xml$/.test(e));for(let e of n){let n=t.file(e);if(!n)continue;let r=(await n.async(`string`)).replace(/<a:picLocks([^/]*)\/>/g,(e,t)=>{let n=e=>RegExp(`\\b${e}=`).test(t);return`<a:picLocks${t}${[`noMove`,`noResize`,`noSelect`].filter(e=>!n(e)).map(e=>` ${e}="1"`).join(``)}/>`});t.file(e,r)}return t.generateAsync({type:`uint8array`,compression:`DEFLATE`})}function qN({snapshot:e}){let{t}=O(),[n,r]=(0,y.useState)(`all`),[i,a]=(0,y.useState)(()=>new Set),o=e.screens,s=n===`all`?o:o.filter(e=>i.has(e.name)),c=s.reduce((e,t)=>e+t.summary.total,0),l=e=>e.reduce((e,t)=>e+Math.max(1,t.features?.length??1),0),u=l(s),d=l(o),f=e=>{let t=new Set(i);t.has(e)?t.delete(e):t.add(e),a(t)},p=()=>a(new Set(o.map(e=>e.name))),m=()=>a(new Set),h=i.size===o.length&&o.length>0;return(0,M.jsxs)(`div`,{className:`space-y-6 max-w-3xl`,children:[(0,M.jsxs)(`div`,{className:`bg-white dark:bg-night-800 rounded-xl border border-ink-100 dark:border-night-700 p-6 shadow-sm space-y-5`,children:[(0,M.jsxs)(`div`,{children:[(0,M.jsx)(`h2`,{className:`text-lg font-semibold mb-1`,children:t(`export.title`)}),(0,M.jsx)(`p`,{className:`text-xs text-ink-500 dark:text-night-500`,children:t(`export.subtitle`)})]}),(0,M.jsxs)(`div`,{className:`space-y-3`,children:[(0,M.jsx)(YN,{children:t(`export.scope`)}),(0,M.jsxs)(`div`,{className:`flex flex-wrap gap-3`,children:[(0,M.jsx)(JN,{checked:n===`all`,onChange:()=>r(`all`),label:t(`export.allScreens`),hint:t(`export.allScreens.hint`,{screens:o.length,files:d,tcs:e.summary.total})}),(0,M.jsx)(JN,{checked:n===`selected`,onChange:()=>r(`selected`),label:t(`export.selectedScreens`),hint:t(`export.selectedScreens.hint`)})]})]}),n===`selected`&&(0,M.jsxs)(`div`,{className:`space-y-2`,children:[(0,M.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-2`,children:[(0,M.jsx)(YN,{children:t(`export.screens`)}),(0,M.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,M.jsx)(`span`,{className:`text-xs text-ink-500 dark:text-night-500 tabular-nums`,children:t(`export.selectedCount`,{selected:i.size,total:o.length})}),(0,M.jsx)(`button`,{type:`button`,onClick:p,disabled:h,className:`text-xs px-2 py-1 rounded-md border border-ink-100 dark:border-night-700 hover:bg-ink-100 dark:hover:bg-night-700 disabled:opacity-40 disabled:cursor-not-allowed`,children:t(`export.selectAll`)}),(0,M.jsx)(`button`,{type:`button`,onClick:m,disabled:i.size===0,className:`text-xs px-2 py-1 rounded-md border border-ink-100 dark:border-night-700 hover:bg-ink-100 dark:hover:bg-night-700 disabled:opacity-40 disabled:cursor-not-allowed`,children:t(`export.deselectAll`)})]})]}),(0,M.jsx)(`div`,{className:`grid grid-cols-2 sm:grid-cols-3 gap-2`,children:o.map(e=>{let n=i.has(e.name),r=e.features?.length??0;return(0,M.jsxs)(`label`,{className:`flex items-center gap-2 px-3 py-2 rounded-lg border cursor-pointer ${n?`bg-brand-50 dark:bg-brand-700/20 border-brand-500`:`bg-white dark:bg-night-900 border-ink-100 dark:border-night-700`}`,children:[(0,M.jsx)(`input`,{type:`checkbox`,checked:n,onChange:()=>f(e.name),className:`accent-brand-600`}),(0,M.jsx)(`span`,{className:`text-sm font-medium`,children:e.name}),r>1&&(0,M.jsx)(`span`,{className:`text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded-md bg-brand-100 dark:bg-brand-700/30 text-brand-700 dark:text-brand-100`,children:t(`export.featuresBadge`,{n:r})}),(0,M.jsx)(`span`,{className:`text-xs text-ink-500 dark:text-night-500 ml-auto`,children:e.summary.total})]},e.name)})})]}),(0,M.jsxs)(`div`,{className:`border-t border-ink-100 dark:border-night-700 pt-5 flex flex-wrap items-center gap-3`,children:[(0,M.jsxs)(`button`,{type:`button`,onClick:()=>{AN(e,{screens:n===`all`?void 0:Array.from(i)})},disabled:s.length===0,className:`px-4 py-2 rounded-lg bg-white dark:bg-night-900 border border-ink-100 dark:border-night-700 text-sm font-medium hover:bg-ink-100 dark:hover:bg-night-700 disabled:opacity-40 disabled:cursor-not-allowed inline-flex items-center gap-2`,children:[(0,M.jsx)(XN,{}),t(`export.downloadCsv`)]}),(0,M.jsxs)(`button`,{type:`button`,onClick:()=>{MN(e,{screens:n===`all`?void 0:Array.from(i)})},disabled:s.length===0,className:`px-4 py-2 rounded-lg bg-brand-600 text-white text-sm font-medium hover:bg-brand-700 disabled:opacity-40 disabled:cursor-not-allowed inline-flex items-center gap-2`,children:[(0,M.jsx)(XN,{}),t(`export.downloadXlsx`)]}),(0,M.jsx)(`span`,{className:`text-sm text-ink-500 dark:text-night-500 ml-auto`,children:t(`export.summary`,{screens:s.length,files:u,tcs:c})})]})]}),(0,M.jsx)(ZN,{})]})}function JN({checked:e,onChange:t,label:n,hint:r}){return(0,M.jsxs)(`button`,{type:`button`,onClick:t,className:`flex-1 min-w-[180px] text-left px-4 py-3 rounded-lg border transition-colors ${e?`bg-brand-50 dark:bg-brand-700/20 border-brand-500 ring-1 ring-brand-500`:`bg-white dark:bg-night-900 border-ink-100 dark:border-night-700 hover:border-ink-300 dark:hover:border-night-600`}`,children:[(0,M.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,M.jsx)(`span`,{className:`w-3 h-3 rounded-full border-2 ${e?`border-brand-600 bg-brand-600`:`border-ink-300 dark:border-night-500`}`}),(0,M.jsx)(`span`,{className:`text-sm font-medium`,children:n})]}),(0,M.jsx)(`div`,{className:`text-xs text-ink-500 dark:text-night-500 mt-1 ml-5`,children:r})]})}function YN({children:e}){return(0,M.jsx)(`div`,{className:`text-xs font-semibold uppercase tracking-wider text-ink-500 dark:text-night-500`,children:e})}function XN(){return(0,M.jsxs)(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,M.jsx)(`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`}),(0,M.jsx)(`polyline`,{points:`7 10 12 15 17 10`}),(0,M.jsx)(`line`,{x1:`12`,y1:`15`,x2:`12`,y2:`3`})]})}function ZN(){let{t:e}=O();return(0,M.jsxs)(`div`,{className:`bg-brand-50 dark:bg-brand-700/15 border border-brand-100 dark:border-brand-700/40 rounded-xl p-4 text-sm text-ink-700 dark:text-night-300`,children:[(0,M.jsx)(`div`,{className:`font-semibold mb-1`,children:e(`export.tip.title`)}),(0,M.jsx)(`code`,{className:`font-mono text-xs`,children:`sungen delivery [name…]`}),(0,M.jsxs)(`span`,{className:`text-ink-500 dark:text-night-500`,children:[e(`export.tip.body`),(0,M.jsx)(`span`,{className:`font-mono`,children:`qa/deliverables/`})]})]})}function QN(){let e=(0,y.useMemo)(sM,[]),[t,n]=(0,y.useState)(`overview`),[r,i]=(0,y.useState)(`all`),[a,o]=(0,y.useState)(null),s=(0,y.useRef)(0),{theme:c,toggle:l}=tM(),{t:u}=O();if(!e)return(0,M.jsx)(`div`,{className:`min-h-screen flex items-center justify-center p-6`,children:(0,M.jsx)(F,{title:u(`empty.title`),body:u(`empty.body`),hint:u(`empty.hint`)})});let{current:d,history:f}=e,p=f.length>0,m=d.screens.filter(e=>!rM(e)).length,h=d.screens.filter(e=>rM(e)).length,g=m>0&&h>0,_=g?r:`all`,v=(0,y.useMemo)(()=>oM(d,_),[d,_]),b=(0,y.useMemo)(()=>({...e,current:v}),[e,v]);return(0,M.jsxs)(`div`,{className:`min-h-screen flex`,children:[(0,M.jsx)(P,{active:t,onChange:n,hasHistory:p}),(0,M.jsxs)(`div`,{className:`flex-1 flex flex-col min-w-0`,children:[(0,M.jsx)(eP,{current:d,theme:c,onToggleTheme:l}),g&&(0,M.jsx)($N,{value:r,onChange:i,allCount:m+h,webCount:m,mobileCount:h}),(0,M.jsxs)(`main`,{className:`flex-1 p-6 overflow-x-hidden`,children:[t===`overview`&&(0,M.jsx)(dM,{payload:b,theme:c,onSelectScreen:e=>{s.current+=1,o({name:e,nonce:s.current}),n(`suites`)}}),t===`suites`&&(0,M.jsx)(MM,{snapshot:v,focusScreen:a}),t===`trends`&&(0,M.jsx)(WM,{payload:e,theme:c}),t===`compare`&&(0,M.jsx)(XM,{payload:e}),t===`export`&&(0,M.jsx)(qN,{snapshot:v})]})]})]})}function $N({value:e,onChange:t,allCount:n,webCount:r,mobileCount:i}){return(0,M.jsx)(`div`,{className:`bg-white dark:bg-night-800 border-b border-ink-100 dark:border-night-700 px-6 pt-3 flex gap-1`,children:[{key:`all`,label:`All`,count:n},{key:`web`,label:`🌐 Web`,count:r},{key:`mobile`,label:`📱 Mobile`,count:i}].map(n=>(0,M.jsxs)(`button`,{onClick:()=>t(n.key),className:`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${e===n.key?`border-brand-500 text-brand-600 dark:text-brand-500`:`border-transparent text-ink-500 dark:text-night-500 hover:text-ink-700 dark:hover:text-night-300`}`,children:[n.label,(0,M.jsx)(`span`,{className:`ml-1.5 text-xs text-ink-400 dark:text-night-500`,children:n.count})]},n.key))})}function eP({current:e,theme:t,onToggleTheme:n}){let{t:r}=O();return(0,M.jsxs)(`header`,{className:`bg-white dark:bg-night-800 border-b border-ink-100 dark:border-night-700 px-6 py-4 flex flex-wrap items-center gap-4`,children:[(0,M.jsxs)(`div`,{children:[(0,M.jsx)(`div`,{className:`text-xs uppercase tracking-wider text-ink-500 dark:text-night-500`,children:r(`header.run`)}),(0,M.jsx)(`div`,{className:`text-sm font-mono font-medium`,children:qj(e.runId)})]}),(0,M.jsx)(nP,{}),(0,M.jsx)(tP,{label:r(`header.environment`),value:e.environment.baseURL||`—`}),(0,M.jsx)(tP,{label:r(`header.project`),value:e.environment.projectName||`—`}),e.environment.gitBranch&&(0,M.jsx)(tP,{label:r(`header.branch`),value:e.environment.gitBranch,mono:!0}),(0,M.jsxs)(`div`,{className:`ml-auto flex items-center gap-2`,children:[(0,M.jsxs)(`div`,{className:`text-xs text-ink-500 dark:text-night-500 mr-1`,children:[`sungen v`,e.environment.sungenVersion]}),(0,M.jsx)(z,{}),(0,M.jsx)(I,{theme:t,onToggle:n})]})]})}function tP({label:e,value:t,mono:n}){return(0,M.jsxs)(`div`,{className:`min-w-0`,children:[(0,M.jsx)(`div`,{className:`text-xs uppercase tracking-wider text-ink-500 dark:text-night-500`,children:e}),(0,M.jsx)(`div`,{className:`text-sm truncate max-w-[260px] ${n?`font-mono`:``}`,title:t,children:t})]})}function nP(){return(0,M.jsx)(`div`,{className:`hidden sm:block w-px h-8 bg-ink-100 dark:bg-night-700`})}function rP(){let{locale:e,setLocale:t}=k();return(0,M.jsx)(A,{value:(0,y.useMemo)(()=>({locale:e,setLocale:t,t:(t,n)=>E(e,t,n)}),[e]),children:(0,M.jsx)(QN,{})})}(0,v.createRoot)(document.getElementById(`root`)).render((0,M.jsx)(y.StrictMode,{children:(0,M.jsx)(rP,{})}));</script>
|
|
126
126
|
<style rel="stylesheet" crossorigin>*,:before,:after{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Helvetica Neue,sans-serif;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button{-webkit-appearance:button;background-color:#0000;background-image:none}input:where([type=button]){-webkit-appearance:button;background-color:#0000;background-image:none}input:where([type=reset]){-webkit-appearance:button;background-color:#0000;background-image:none}input:where([type=submit]){-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html,body,#root{height:100%}body{font-feature-settings:"cv11", "ss01"}body,.stat-card,.nav-link{transition:background-color .15s,border-color .15s,color .15s}.stat-card{--tw-border-opacity:1;border-width:1px;border-color:rgb(241 245 249/var(--tw-border-opacity,1));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000), var(--tw-ring-shadow,0 0 #0000), var(--tw-shadow);border-radius:.75rem;flex-direction:column;gap:.25rem;padding:1.25rem;display:flex}.stat-card:is(.dark *){--tw-border-opacity:1;border-color:rgb(31 42 68/var(--tw-border-opacity,1));--tw-bg-opacity:1;background-color:rgb(17 26 44/var(--tw-bg-opacity,1))}.stat-label{text-transform:uppercase;letter-spacing:.05em;--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1));font-size:.75rem;font-weight:500;line-height:1rem}.stat-label:is(.dark *){--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.stat-value{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);font-size:1.875rem;font-weight:600;line-height:2.25rem}.pill{border-radius:.375rem;align-items:center;padding:.125rem .5rem;font-size:.75rem;font-weight:500;line-height:1rem;display:inline-flex}.nav-link{cursor:pointer;--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1));border-radius:.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:500;line-height:1.25rem}.nav-link:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.nav-link:is(.dark *){--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.nav-link:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 42 68/var(--tw-bg-opacity,1))}.nav-link-active{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1));--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1))}.nav-link-active:hover{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.nav-link-active:is(.dark *){--tw-text-opacity:1;color:rgb(219 234 254/var(--tw-text-opacity,1));background-color:#1d4ed833}.nav-link-active:hover:is(.dark *){background-color:#1d4ed84d}.dark .scrollbar-thin::-webkit-scrollbar-thumb{background:#1f2a44}.dark .scrollbar-thin::-webkit-scrollbar-thumb:hover{background:#2a3654}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;bottom:0;left:0;right:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.right-0{right:0}.top-0{top:0}.top-1\/2{top:50%}.top-full{top:100%}.z-40{z-index:40}.z-50{z-index:50}.col-span-3{grid-column:span 3/span 3}.col-span-6{grid-column:span 6/span 6}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mx-auto{margin-left:auto;margin-right:auto}.-mb-px{margin-bottom:-1px}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-1\.5{margin-left:.375rem}.ml-5{margin-left:1.25rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-5{height:1.25rem}.h-56{height:14rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-8{height:2rem}.h-9{height:2.25rem}.min-h-\[400px\]{min-height:400px}.min-h-screen{min-height:100vh}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-8{width:2rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[180px\]{min-width:180px}.min-w-\[200px\]{min-width:200px}.max-w-3xl{max-width:48rem}.max-w-\[260px\]{max-width:260px}.max-w-md{max-width:28rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate:180deg;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate:90deg;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.scroll-mt-4{scroll-margin-top:1rem}.list-decimal{list-style-type:decimal}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-ink-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(241 245 249/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-bad-500\/40{border-color:#dc262666}.border-brand-100{--tw-border-opacity:1;border-color:rgb(219 234 254/var(--tw-border-opacity,1))}.border-brand-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-brand-500\/40{border-color:#3b82f666}.border-brand-600{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity,1))}.border-emerald-300\/60{border-color:#6ee7b799}.border-ink-100{--tw-border-opacity:1;border-color:rgb(241 245 249/var(--tw-border-opacity,1))}.border-ink-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.border-ok-500\/40{border-color:#16a34a66}.border-purple-500\/40{border-color:#a855f766}.border-transparent{border-color:#0000}.border-warn-500\/40{border-color:#d9770666}.bg-amber-100{--tw-bg-opacity:1;background-color:rgb(254 243 199/var(--tw-bg-opacity,1))}.bg-bad-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-bad-100\/30{background-color:#fee2e24d}.bg-bad-500{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-brand-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-brand-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.bg-brand-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-brand-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-100{--tw-bg-opacity:1;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-ink-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.bg-ink-100\/50{background-color:#f1f5f980}.bg-ink-300{--tw-bg-opacity:1;background-color:rgb(203 213 225/var(--tw-bg-opacity,1))}.bg-ink-500{--tw-bg-opacity:1;background-color:rgb(100 116 139/var(--tw-bg-opacity,1))}.bg-ink-900\/50{background-color:#0f172a80}.bg-ok-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity,1))}.bg-ok-100\/30{background-color:#dcfce74d}.bg-ok-500{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity,1))}.bg-purple-100{--tw-bg-opacity:1;background-color:rgb(243 232 255/var(--tw-bg-opacity,1))}.bg-purple-500{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity,1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-sky-100{--tw-bg-opacity:1;background-color:rgb(224 242 254/var(--tw-bg-opacity,1))}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity,1))}.bg-warn-100{--tw-bg-opacity:1;background-color:rgb(254 243 199/var(--tw-bg-opacity,1))}.bg-warn-500{--tw-bg-opacity:1;background-color:rgb(217 119 6/var(--tw-bg-opacity,1))}.bg-warn-500\/10{background-color:#d977061a}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/60{background-color:#fff9}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pl-12{padding-left:3rem}.pl-20{padding-left:5rem}.pl-5{padding-left:1.25rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pr-5{padding-right:1.25rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.font-sans{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Helvetica Neue,sans-serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-bad-500{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-brand-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-brand-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1))}.text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.text-ink-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-ink-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-ink-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-ink-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.text-ok-500{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-purple-700{--tw-text-opacity:1;color:rgb(126 34 206/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-sky-700{--tw-text-opacity:1;color:rgb(3 105 161/var(--tw-text-opacity,1))}.text-warn-500{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/80{color:#fffc}.line-through{text-decoration-line:line-through}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-brand-600{accent-color:#2563eb}.opacity-35{opacity:.35}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.shadow-2xl{--tw-shadow:0 25px 50px -12px #00000040;--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000), var(--tw-ring-shadow,0 0 #0000), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000), var(--tw-ring-shadow,0 0 #0000), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000), var(--tw-ring-shadow,0 0 #0000), var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow,0 0 #0000)}.ring-brand-500{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.invert{--tw-invert:invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.\!filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-property:transform;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-700{transition-duration:.7s}.placeholder\:text-ink-500::placeholder{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.hover\:border-ink-300:hover{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.hover\:bg-brand-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-ink-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.hover\:bg-ink-100\/40:hover{background-color:#f1f5f966}.hover\:bg-ink-100\/50:hover{background-color:#f1f5f980}.hover\:bg-ink-100\/60:hover{background-color:#f1f5f999}.hover\:bg-ink-300\/60:hover{background-color:#cbd5e199}.hover\:text-ink-700:hover{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.focus\:border-transparent:focus{border-color:#0000}.focus\:outline-none:focus{outline-offset:2px;outline:2px solid #0000}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow,0 0 #0000)}.focus\:ring-brand-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.focus-visible\:outline:focus-visible{outline-style:solid}.focus-visible\:outline-2:focus-visible{outline-width:2px}.focus-visible\:outline-brand-500:focus-visible{outline-color:#3b82f6}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.dark\:divide-night-700:is(.dark *)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(31 42 68/var(--tw-divide-opacity,1))}.dark\:border-amber-500\/40:is(.dark *){border-color:#f59e0b66}.dark\:border-brand-500\/40:is(.dark *){border-color:#3b82f666}.dark\:border-brand-700\/40:is(.dark *){border-color:#1d4ed866}.dark\:border-emerald-500\/40:is(.dark *){border-color:#10b98166}.dark\:border-emerald-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(4 120 87/var(--tw-border-opacity,1))}.dark\:border-night-500:is(.dark *){--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity,1))}.dark\:border-night-600:is(.dark *){--tw-border-opacity:1;border-color:rgb(42 54 84/var(--tw-border-opacity,1))}.dark\:border-night-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(31 42 68/var(--tw-border-opacity,1))}.dark\:border-purple-500\/40:is(.dark *){border-color:#a855f766}.dark\:border-red-500\/40:is(.dark *){border-color:#ef444466}.dark\:bg-amber-500\/15:is(.dark *){background-color:#f59e0b26}.dark\:bg-black\/70:is(.dark *){background-color:#000000b3}.dark\:bg-brand-500:is(.dark *){--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.dark\:bg-brand-700\/15:is(.dark *){background-color:#1d4ed826}.dark\:bg-brand-700\/20:is(.dark *){background-color:#1d4ed833}.dark\:bg-brand-700\/25:is(.dark *){background-color:#1d4ed840}.dark\:bg-brand-700\/30:is(.dark *){background-color:#1d4ed84d}.dark\:bg-emerald-400:is(.dark *){--tw-bg-opacity:1;background-color:rgb(52 211 153/var(--tw-bg-opacity,1))}.dark\:bg-emerald-500\/15:is(.dark *){background-color:#10b98126}.dark\:bg-emerald-500\/5:is(.dark *){background-color:#10b9810d}.dark\:bg-emerald-900\/40:is(.dark *){background-color:#064e3b66}.dark\:bg-night-500:is(.dark *){--tw-bg-opacity:1;background-color:rgb(148 163 184/var(--tw-bg-opacity,1))}.dark\:bg-night-700:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 42 68/var(--tw-bg-opacity,1))}.dark\:bg-night-700\/50:is(.dark *){background-color:#1f2a4480}.dark\:bg-night-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(17 26 44/var(--tw-bg-opacity,1))}.dark\:bg-night-900:is(.dark *){--tw-bg-opacity:1;background-color:rgb(11 18 32/var(--tw-bg-opacity,1))}.dark\:bg-night-900\/20:is(.dark *){background-color:#0b122033}.dark\:bg-night-900\/30:is(.dark *){background-color:#0b12204d}.dark\:bg-night-900\/40:is(.dark *){background-color:#0b122066}.dark\:bg-purple-500\/15:is(.dark *){background-color:#a855f726}.dark\:bg-red-400:is(.dark *){--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity,1))}.dark\:bg-red-500\/15:is(.dark *){background-color:#ef444426}.dark\:bg-red-500\/5:is(.dark *){background-color:#ef44440d}.dark\:bg-sky-500\/15:is(.dark *){background-color:#0ea5e926}.dark\:bg-sky-900\/40:is(.dark *){background-color:#0c4a6e66}.dark\:text-amber-300:is(.dark *){--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.dark\:text-brand-100:is(.dark *){--tw-text-opacity:1;color:rgb(219 234 254/var(--tw-text-opacity,1))}.dark\:text-brand-500:is(.dark *){--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.dark\:text-emerald-300:is(.dark *){--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.dark\:text-night-100:is(.dark *){--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.dark\:text-night-300:is(.dark *){--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.dark\:text-night-500:is(.dark *){--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.dark\:text-purple-300:is(.dark *){--tw-text-opacity:1;color:rgb(216 180 254/var(--tw-text-opacity,1))}.dark\:text-red-300:is(.dark *){--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.dark\:text-sky-300:is(.dark *){--tw-text-opacity:1;color:rgb(125 211 252/var(--tw-text-opacity,1))}.dark\:placeholder\:text-night-500:is(.dark *)::-moz-placeholder{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.dark\:placeholder\:text-night-500:is(.dark *)::placeholder{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.dark\:hover\:border-night-600:hover:is(.dark *){--tw-border-opacity:1;border-color:rgb(42 54 84/var(--tw-border-opacity,1))}.dark\:hover\:bg-night-600:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(42 54 84/var(--tw-bg-opacity,1))}.dark\:hover\:bg-night-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 42 68/var(--tw-bg-opacity,1))}.dark\:hover\:bg-night-700\/30:hover:is(.dark *){background-color:#1f2a444d}.dark\:hover\:bg-night-700\/40:hover:is(.dark *){background-color:#1f2a4466}.dark\:hover\:text-night-300:hover:is(.dark *){--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-span-7{grid-column:span 7/span 7}.sm\:block{display:block}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:768px){.md\:inline-flex{display:inline-flex}}@media (min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}@media (min-width:1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|
|
127
127
|
/*$vite$:1*/</style>
|
package/src/dashboard/types.ts
CHANGED
|
@@ -144,6 +144,7 @@ export interface ScenarioSnapshot {
|
|
|
144
144
|
duration?: number; // ms
|
|
145
145
|
errorMessage?: string;
|
|
146
146
|
tracePath?: string;
|
|
147
|
+
manualReason?: string; // `# MANUAL: …` reason → Note column of a Pending manual row
|
|
147
148
|
executedDate?: string; // dd/mm/yyyy
|
|
148
149
|
testEnvironment?: string;
|
|
149
150
|
testExecutor?: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { getPackageVersion } from './package-info';
|
|
9
|
-
import { MergedScenario } from './scenario-merger';
|
|
9
|
+
import { MergedScenario, extractManualReason } from './scenario-merger';
|
|
10
10
|
import {
|
|
11
11
|
extractAuthRole,
|
|
12
12
|
extractPriority,
|
|
@@ -27,10 +27,15 @@ import { collectSecretValues, formatApiCallSummaryLines, formatApiExpected, form
|
|
|
27
27
|
import { SelectorKeyMap, substituteSelectorKeysInStep } from './selector-key-resolver';
|
|
28
28
|
import { formatVariantRow, resolveResultVariants } from './result-variants';
|
|
29
29
|
import { collectQaEnvSecretDenylist, warnIfRedactionIncomplete } from './qa-env-secret-denylist';
|
|
30
|
+
import { formatEnv } from './run-environment';
|
|
30
31
|
|
|
31
32
|
export interface BuildCsvInput {
|
|
32
33
|
screen: string;
|
|
33
34
|
featureName: string;
|
|
35
|
+
/** The exact command that regenerates this unit (`sungen generate --flow checkout`). `screen`
|
|
36
|
+
* is the deliverable label — the FEATURE basename, `flow/<x>` for a flow — so a note built from
|
|
37
|
+
* it named a command that fails (#880). */
|
|
38
|
+
generateCommand?: string;
|
|
34
39
|
/** URL path for the screen/flow entry point — concatenated onto baseURL
|
|
35
40
|
* when building the Test Environment column so QA can see the exact URL
|
|
36
41
|
* the test targets. */
|
|
@@ -206,7 +211,12 @@ export function buildTestCaseRows(input: BuildCsvInput): TestCaseRow[] {
|
|
|
206
211
|
// No execution record AND no compiled test → Manual if @manual, else genuinely not compiled.
|
|
207
212
|
testResult = testcaseType === 'Manual' ? 'Pending' : 'N/A';
|
|
208
213
|
if (testcaseType !== 'Manual') {
|
|
209
|
-
note =
|
|
214
|
+
note = `Scenario not compiled — re-run \`${input.generateCommand ?? `sungen generate --screen ${input.screen}`}\``;
|
|
215
|
+
} else {
|
|
216
|
+
// Why the case is manual — the `# MANUAL: …` comment — so the tester reading the
|
|
217
|
+
// deliverable knows what blocks automation (#810). Empty when no reason is written.
|
|
218
|
+
const reason = extractManualReason(m.feature.comments ?? []);
|
|
219
|
+
if (reason) note = redactSecrets(`Manual: ${reason}`, qaEnvSecrets.values);
|
|
210
220
|
}
|
|
211
221
|
} else {
|
|
212
222
|
// Compiled but no execution record → Pending
|
|
@@ -290,15 +300,6 @@ export function buildSummary(screen: string, rows: TestCaseRow[], outputFile: st
|
|
|
290
300
|
* the baseURL so the deliverable shows the exact URL each test targets
|
|
291
301
|
* (e.g. `https://staging.example.com/kudos (chromium)`).
|
|
292
302
|
*/
|
|
293
|
-
function formatEnv(baseURL: string, featurePath: string | undefined, projectName: string): string {
|
|
294
|
-
const base = (baseURL || '').replace(/\/+$/, '');
|
|
295
|
-
const p = (featurePath || '').replace(/^\/+/, '');
|
|
296
|
-
// Join with '/' only when there is a baseURL (web). For mobile baseURL is empty
|
|
297
|
-
// and `p` is an app id (e.g. com.kngroup.media.converter) — emit it bare, no '/'.
|
|
298
|
-
const url = base && p ? `${base}/${p}` : (p || base);
|
|
299
|
-
return `${url} (${projectName})`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
303
|
function csvCell(v: string | number | undefined | null): string {
|
|
303
304
|
if (v === null || v === undefined) return '';
|
|
304
305
|
const s = String(v);
|
|
@@ -117,6 +117,7 @@ export function parseFeatureMetadata(featureFilePath: string): FeatureMetadata {
|
|
|
117
117
|
referencedVars: extractReferencedVars(sc, extractTestcaseType([...parsed.tags, ...sc.tags]) === 'Manual'),
|
|
118
118
|
orderedSteps: classifySteps(sc),
|
|
119
119
|
comments: sc.comments,
|
|
120
|
+
hookType: sc.hookType,
|
|
120
121
|
}));
|
|
121
122
|
|
|
122
123
|
return {
|
|
@@ -136,6 +137,17 @@ export function isStepsBaseScenario(sc: ScenarioMetadata): boolean {
|
|
|
136
137
|
return !!sc.stepsName;
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
/**
|
|
141
|
+
* A hook (`@beforeAll` / `@afterEach` / `@afterAll`) — setup or teardown around the tests, compiled
|
|
142
|
+
* to `test.beforeAll()` etc. Read from the scenario's OWN tag (the parser's `hookType`), never from
|
|
143
|
+
* the feature+scenario tag union. It is not a test case: counted as one, it blocked the matrix at
|
|
144
|
+
* Gate A (a hook carries no VP id, as the gherkin skill tells authors) and showed in the legacy
|
|
145
|
+
* workbook and the dashboard as a "Not compiled" row (#880).
|
|
146
|
+
*/
|
|
147
|
+
export function isHookScenario(sc: ScenarioMetadata): boolean {
|
|
148
|
+
return !!sc.hookType;
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
/**
|
|
140
152
|
* Return true if this is the default scaffold sample scenario (not real).
|
|
141
153
|
*/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* richer ScenarioSnapshot / ScreenSnapshot shape used by the dashboard UI.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { MergedScenario } from './scenario-merger';
|
|
8
|
+
import { MergedScenario, extractManualReason } from './scenario-merger';
|
|
9
9
|
import {
|
|
10
10
|
extractAuthRole,
|
|
11
11
|
extractPriority,
|
|
@@ -192,6 +192,7 @@ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnap
|
|
|
192
192
|
let testEnvironment: string | undefined;
|
|
193
193
|
let testExecutor: string | undefined;
|
|
194
194
|
let type: ScenarioSnapshot['type'];
|
|
195
|
+
let manualReason: string | undefined;
|
|
195
196
|
|
|
196
197
|
if (result) {
|
|
197
198
|
// A result exists → the scenario compiled AND ran; authoritative over the `!m.spec`
|
|
@@ -224,6 +225,9 @@ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnap
|
|
|
224
225
|
// No result AND no compiled test → Manual if @manual, else genuinely not compiled.
|
|
225
226
|
type = baseType === 'Manual' ? 'Manual' : 'Not compiled';
|
|
226
227
|
status = baseType === 'Manual' ? 'Pending' : 'N/A';
|
|
228
|
+
// Same Note source as csv-exporter: the `# MANUAL: …` reason (#810).
|
|
229
|
+
const reason = baseType === 'Manual' ? extractManualReason(m.feature.comments ?? []) : '';
|
|
230
|
+
if (reason) manualReason = redactSecrets(reason, qaEnvSecrets.values);
|
|
227
231
|
} else {
|
|
228
232
|
// Compiled but no execution record → Pending.
|
|
229
233
|
type = baseType;
|
|
@@ -249,6 +253,7 @@ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnap
|
|
|
249
253
|
executedDate,
|
|
250
254
|
errorMessage,
|
|
251
255
|
tracePath,
|
|
256
|
+
manualReason,
|
|
252
257
|
testEnvironment,
|
|
253
258
|
testExecutor,
|
|
254
259
|
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The @manual "presence placeholder" hybrid (#810) — one predicate for every stage.
|
|
3
|
+
*
|
|
4
|
+
* Shape: a @manual scenario keeps complete Gherkin, but its only assertions are bare presence
|
|
5
|
+
* checks (`Then User see [Job List] table`), while the REAL oracle ("the newer row appears above
|
|
6
|
+
* the older one") sits in an unlabelled `# Tester verifies:` comment. Both gates only asked "is
|
|
7
|
+
* there a Then?", and the exporter only captures numbered comment lines — so the deliverable said
|
|
8
|
+
* "See [Job List] table" and a tester ticking it Passed recorded a check nobody made.
|
|
9
|
+
*
|
|
10
|
+
* The compile gate (SG-W008), the audit gate (MANUAL-STEPS-INSUFFICIENT) and both exporters read
|
|
11
|
+
* the comment block through THIS module, so the three can never disagree about what the oracle is.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface KeywordStep {
|
|
15
|
+
/** Gherkin keyword as written: Given / When / Then / And / But (surrounding space allowed). */
|
|
16
|
+
keyword: string;
|
|
17
|
+
/** Step text without the keyword. */
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const NUMBERED = /^\d+[.)]\s*/;
|
|
22
|
+
const DIVIDER = /^[-=*_]{2,}/;
|
|
23
|
+
const HEADER = /^tester verifies\b\s*:?\s*(.*)$/i;
|
|
24
|
+
const LABELLED_ORACLE = /^\d+[.)]\s*[A-Za-z /]*?(observ|oracle|expect|result|then|assert)[A-Za-z /]*:/i;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The prose a `Tester verifies:` header carries when it is NOT written as numbered lines — the
|
|
28
|
+
* text on the header line itself plus the lines after it, up to the first numbered item or divider.
|
|
29
|
+
* Returns null when the block is empty (a bare header followed by numbered items is the structured
|
|
30
|
+
* shape, not this one) or when there is no header at all. Only the FIRST header's block is read —
|
|
31
|
+
* a second `Tester verifies:` ends it.
|
|
32
|
+
*/
|
|
33
|
+
export function extractUnlabeledOracle(comments: string[]): string | null {
|
|
34
|
+
const parts: string[] = [];
|
|
35
|
+
let inBlock = false;
|
|
36
|
+
for (const raw of comments) {
|
|
37
|
+
const line = raw.trim();
|
|
38
|
+
if (!inBlock) {
|
|
39
|
+
const m = line.match(HEADER);
|
|
40
|
+
if (!m) continue;
|
|
41
|
+
inBlock = true;
|
|
42
|
+
if (m[1].trim()) parts.push(m[1].trim());
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!line) continue;
|
|
46
|
+
if (NUMBERED.test(line) || DIVIDER.test(line) || HEADER.test(line)) break;
|
|
47
|
+
parts.push(line);
|
|
48
|
+
}
|
|
49
|
+
const text = parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
50
|
+
return text ? text : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** True when the comments state an oracle — a labelled numbered line, or unlabelled header prose. */
|
|
54
|
+
export function hasCommentOracle(comments: string[]): boolean {
|
|
55
|
+
return comments.some((c) => LABELLED_ORACLE.test(c.trim())) || extractUnlabeledOracle(comments) !== null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A bare presence assertion: the element exists, nothing about its content, order, count or state.
|
|
60
|
+
* `User see [Job List] table` / `User is on [Home] page` / `User see [Save] button is visible`.
|
|
61
|
+
* Anything after the element type (`match data:`, `with {{v}}`, `row 2`, `is sorted …`) is a real check.
|
|
62
|
+
*/
|
|
63
|
+
export function isPresenceOnlyAssertion(text: string): boolean {
|
|
64
|
+
return /^User (?:see|is on) \[[^\]]+\](?: [a-z]+)?(?: (?:is|are) (?:visible|displayed|shown))?\s*$/i.test(text.trim());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* True when no assertion checks anything beyond presence — including when there is no assertion
|
|
69
|
+
* at all (a documentation-style manual). Only then may unlabelled comment prose stand in as the
|
|
70
|
+
* oracle: a Then that already asserts content/order/count IS the oracle, and replacing it with the
|
|
71
|
+
* prose would drop the real check from the deliverable.
|
|
72
|
+
*/
|
|
73
|
+
export function onlyPresenceAssertions(assertionTexts: string[]): boolean {
|
|
74
|
+
return assertionTexts.every(isPresenceOnlyAssertion);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The scenario's assertion steps — a Then, plus every And/But whose preceding explicit keyword is Then. */
|
|
78
|
+
export function assertionSteps(steps: KeywordStep[]): KeywordStep[] {
|
|
79
|
+
let bucket = '';
|
|
80
|
+
const out: KeywordStep[] = [];
|
|
81
|
+
for (const s of steps) {
|
|
82
|
+
const kw = s.keyword.trim();
|
|
83
|
+
if (kw === 'Given' || kw === 'When' || kw === 'Then') bucket = kw;
|
|
84
|
+
if (bucket === 'Then') out.push(s);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The banned hybrid: ≥1 assertion, every assertion a bare presence placeholder, and an oracle in
|
|
91
|
+
* the comments. A @manual whose Gherkin already asserts something real is fine — its comments are
|
|
92
|
+
* an elaboration, not the only statement of what the case checks.
|
|
93
|
+
*/
|
|
94
|
+
export function isPresencePlaceholderHybrid(steps: KeywordStep[], comments: string[]): boolean {
|
|
95
|
+
const asserts = assertionSteps(steps);
|
|
96
|
+
return asserts.length > 0 && onlyPresenceAssertions(asserts.map((s) => s.text)) && hasCommentOracle(comments);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Split raw scenario-block lines into keyword steps (for the text-based audit gate). */
|
|
100
|
+
export function keywordStepsOf(block: string): KeywordStep[] {
|
|
101
|
+
const out: KeywordStep[] = [];
|
|
102
|
+
for (const line of block.split('\n')) {
|
|
103
|
+
const m = line.match(/^\s*(Given|When|Then|And|But)\s+(.+?)\s*$/);
|
|
104
|
+
if (m) out.push({ keyword: m[1], text: m[2] });
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Comment lines of a raw scenario block, with the leading `# ` stripped. */
|
|
110
|
+
export function commentLinesOf(block: string): string[] {
|
|
111
|
+
return block.split('\n').filter((l) => /^\s*#/.test(l)).map((l) => l.replace(/^\s*#\s?/, ''));
|
|
112
|
+
}
|
|
@@ -96,6 +96,7 @@ function resolveDataPairs(
|
|
|
96
96
|
vars: string[],
|
|
97
97
|
testData: Record<string, string> | null,
|
|
98
98
|
row?: Record<string, unknown>,
|
|
99
|
+
runtimeVars: Set<string> = new Set(),
|
|
99
100
|
): string[] {
|
|
100
101
|
const pairs: string[] = [];
|
|
101
102
|
if (row) {
|
|
@@ -107,7 +108,10 @@ function resolveDataPairs(
|
|
|
107
108
|
for (const v of vars) {
|
|
108
109
|
const val = row && v in row ? undefined : testData?.[v]; // row columns already listed
|
|
109
110
|
// Credential-named refs (`accounts.0.password`) render `***` — this column is read outside the run.
|
|
110
|
-
|
|
111
|
+
// A value may embed a var the scenario captures at run time (`Total ({{stat}})`) or a
|
|
112
|
+
// dataset column — expanded with the same vars as the cells, so both columns agree (#879).
|
|
113
|
+
const scope = row ? { ...(testData ?? {}), ...rowAsStrings(row) } : (testData ?? {});
|
|
114
|
+
if (val !== undefined) pairs.push(`${v}: ${displayValue(maskCredentialValue(v, expandValue(String(val), scope, runtimeVars, new Set([v]))))}`);
|
|
111
115
|
}
|
|
112
116
|
return pairs;
|
|
113
117
|
}
|
|
@@ -136,6 +140,10 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
136
140
|
// Test-data values may cross-reference other keys (email_padded: " {{valid_email}} ") —
|
|
137
141
|
// resolve one level so display cells never leak a template token (review B-04).
|
|
138
142
|
const testData = resolveCrossRefs(inputs.testData);
|
|
143
|
+
// Display cells expand from the RAW values, per scenario — with its row and its runtime vars —
|
|
144
|
+
// in the runtime's order (captured var > row column > test-data key). The pre-resolved copy above
|
|
145
|
+
// only serves row discovery and result matching, which render nothing (#879).
|
|
146
|
+
const rawTestData = inputs.testData ?? {};
|
|
139
147
|
const apiCatalog = inputs.apiCatalog ?? {};
|
|
140
148
|
const variants: CoverageVariant[] = [];
|
|
141
149
|
|
|
@@ -158,11 +166,14 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
158
166
|
// fields — Setup → precondition (review B-02), Action → trigger, Observable →
|
|
159
167
|
// expected, Oracle → verification method. Labels never remain inside prose.
|
|
160
168
|
const manual = mode === 'manual' && m.feature.comments?.length
|
|
161
|
-
? classifyManualComments(m.feature.comments)
|
|
169
|
+
? classifyManualComments(m.feature.comments, bucketTexts(m.feature.orderedSteps, 'then'))
|
|
162
170
|
: null;
|
|
163
171
|
|
|
164
172
|
// Raw (pre-wording) step texts — these feed shapes + Gate C, never the cells.
|
|
165
|
-
|
|
173
|
+
// A manual with no numbered action lines keeps its Gherkin When steps as the trigger — the
|
|
174
|
+
// same fallback the oracle uses for Then. Taking comment actions only rendered "No action —
|
|
175
|
+
// the state is checked on page load." for a manual whose When clicks something (#810).
|
|
176
|
+
const rawTrigger = manual && manual.actions.length > 0 ? manual.actions : bucketTexts(m.feature.orderedSteps, 'when');
|
|
166
177
|
const rawOracle = manual
|
|
167
178
|
? (manual.expected.length > 0 ? manual.expected : bucketTexts(m.feature.orderedSteps, 'then'))
|
|
168
179
|
: m.resolvedExpected.filter((s) => s.bucket === 'then').map((s) => s.text);
|
|
@@ -302,14 +313,14 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
302
313
|
const resultByLabel = new Map(resultVariants.map((rv) => [rv.nameSuffix.replace(/^ — /, ''), rv.result]));
|
|
303
314
|
rows.forEach((row, i) => {
|
|
304
315
|
const label = String(row.case ?? row.name ?? row.label ?? `row ${i + 1}`);
|
|
305
|
-
const cells = renderCells({ ...
|
|
316
|
+
const cells = renderCells({ ...rawTestData, ...rowAsStrings(row) });
|
|
306
317
|
variants.push({
|
|
307
318
|
...base,
|
|
308
319
|
ref: `${vpId}#${label}`,
|
|
309
320
|
caseLabel: label,
|
|
310
321
|
title: `${category1} — ${label}`,
|
|
311
322
|
condition: label,
|
|
312
|
-
data: resolveDataPairs(m.feature.referencedVars,
|
|
323
|
+
data: resolveDataPairs(m.feature.referencedVars, rawTestData, row, runtimeVars),
|
|
313
324
|
trigger: cells.trigger,
|
|
314
325
|
oracle: cells.oracle,
|
|
315
326
|
precondition: cells.precondition,
|
|
@@ -319,13 +330,13 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
319
330
|
});
|
|
320
331
|
});
|
|
321
332
|
} else {
|
|
322
|
-
const cells = renderCells(
|
|
333
|
+
const cells = renderCells(rawTestData);
|
|
323
334
|
variants.push({
|
|
324
335
|
...base,
|
|
325
336
|
ref: vpId,
|
|
326
337
|
title: category1,
|
|
327
338
|
condition: category1,
|
|
328
|
-
data: resolveDataPairs(m.feature.referencedVars,
|
|
339
|
+
data: resolveDataPairs(m.feature.referencedVars, rawTestData, undefined, runtimeVars),
|
|
329
340
|
trigger: cells.trigger,
|
|
330
341
|
oracle: cells.oracle,
|
|
331
342
|
precondition: cells.precondition,
|
|
@@ -357,9 +368,9 @@ function substituteDisplayVars(
|
|
|
357
368
|
if (isCapture && /\bas\s+$/i.test(text.slice(0, offset))) return m;
|
|
358
369
|
// A value the test produces at run time has no static form — name it so the
|
|
359
370
|
// tester knows to compare against what the earlier step captured.
|
|
360
|
-
if (
|
|
371
|
+
if (isRuntimeVar(key, runtimeVars)) return `the captured ${key}`;
|
|
361
372
|
if (!(key in vars)) return m; // unknown stays literal → Gate D flags it
|
|
362
|
-
return displayValue(vars[key]);
|
|
373
|
+
return displayValue(expandValue(vars[key], vars, runtimeVars, new Set([key])));
|
|
363
374
|
});
|
|
364
375
|
// A dynamic token written straight into a step (`{{$uuid}}`) never reaches
|
|
365
376
|
// displayValue — name it here so the same wording covers both routes.
|
|
@@ -429,12 +440,45 @@ export function computeRequirements(
|
|
|
429
440
|
});
|
|
430
441
|
}
|
|
431
442
|
|
|
432
|
-
|
|
443
|
+
const isRuntimeVar = (key: string, runtimeVars: Set<string>): boolean =>
|
|
444
|
+
runtimeVars.has(key) || runtimeVars.has(key.split(/[.[]/)[0]);
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Expand the `{{refs}}` inside a test-data VALUE the way the runtime's `interpolate()` does
|
|
448
|
+
* (specs/generated/test-data.ts): recursively, depth-capped against reference cycles, and a var
|
|
449
|
+
* the scenario captures at run time is named, not looked up. An unknown ref (or a cycle) stays
|
|
450
|
+
* literal, so Gate D still refuses it; a nested value renders through displayValue, as it did
|
|
451
|
+
* when this resolved one level (an empty one reads `(empty)`).
|
|
452
|
+
*
|
|
453
|
+
* Delivery used to resolve ONE level, before any scenario's runtime vars were known: both
|
|
454
|
+
* `heading_expected: "Total ({{stat}})"` with `remember … as {{stat}}`, and a plain chain
|
|
455
|
+
* (`a: "{{b}}"`, `b: "x {{c}}"`), left a token that blocked the export of a suite the runtime
|
|
456
|
+
* runs fine (#879).
|
|
457
|
+
*/
|
|
458
|
+
function expandValue(
|
|
459
|
+
value: string, vars: Record<string, string>, runtimeVars: Set<string>,
|
|
460
|
+
expanding: ReadonlySet<string> = new Set(), depth = 0,
|
|
461
|
+
): string {
|
|
462
|
+
if (depth > 10 || !value.includes('{{')) return value;
|
|
463
|
+
return value.replace(/\{\{\s*([^}\s]+)\s*\}\}/g, (m, key: string) => {
|
|
464
|
+
if (isRuntimeVar(key, runtimeVars)) return `the captured ${key}`;
|
|
465
|
+
const v = vars[key];
|
|
466
|
+
// A key already being expanded is a cycle: keep the token (Gate D names it) instead of
|
|
467
|
+
// re-entering — `a: "{{a}}{{a}}"` doubled per level and exhausted the heap.
|
|
468
|
+
if (v === undefined || v === null || expanding.has(key)) return m;
|
|
469
|
+
return displayValue(expandValue(String(v), vars, runtimeVars, new Set([...expanding, key]), depth + 1));
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Pre-resolve test-data cross-references before any scenario is known — every static level.
|
|
475
|
+
* A runtime var is not known yet and stays a token here; each scenario's cells expand it.
|
|
476
|
+
*/
|
|
433
477
|
function resolveCrossRefs(testData: Record<string, string> | null): Record<string, string> | null {
|
|
434
478
|
if (!testData) return null;
|
|
435
479
|
const out: Record<string, string> = {};
|
|
436
480
|
for (const [k, v] of Object.entries(testData)) {
|
|
437
|
-
out[k] = typeof v === 'string' && v.includes('{{') ?
|
|
481
|
+
out[k] = typeof v === 'string' && v.includes('{{') ? renderDynamicTokens(expandValue(v, testData, new Set(), new Set([k]))) : v;
|
|
438
482
|
}
|
|
439
483
|
return out;
|
|
440
484
|
}
|
|
@@ -460,6 +504,7 @@ function rowAsStrings(row: Record<string, unknown>): Record<string, string> {
|
|
|
460
504
|
* as blocked), which silently skewed the derived parent result.
|
|
461
505
|
*/
|
|
462
506
|
export function variantState(v: CoverageVariant): VariantState {
|
|
507
|
+
if (v.manualState) return v.manualState; // a carried Manual result (#891)
|
|
463
508
|
const r = v.result;
|
|
464
509
|
if (!r) return 'Pending';
|
|
465
510
|
switch (r.status) {
|
|
@@ -593,6 +638,7 @@ export function buildMatrix(inputs: BuildInputs): MatrixModel {
|
|
|
593
638
|
[v.ref, v.precondition.filter((line) => !commonPre.includes(line))])),
|
|
594
639
|
triggerDeltas: Object.fromEntries(groupVariants.map((v) =>
|
|
595
640
|
[v.ref, v.trigger.slice(prefixLen)])),
|
|
641
|
+
sharedTriggerSteps: prefixLen,
|
|
596
642
|
// Nothing shared to hoist → say what varies rather than leaving the cell blank.
|
|
597
643
|
trigger: prefixLen > 0 || groupVariants.length === 0
|
|
598
644
|
? (first?.trigger ?? []).slice(0, prefixLen)
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as ExcelJS from 'exceljs';
|
|
22
|
-
import { MatrixModel } from './types';
|
|
22
|
+
import { MatrixModel, VariantState } from './types';
|
|
23
|
+
import { rollUp } from './build';
|
|
23
24
|
import { HEADER_ROW, RESULT_COL } from './render-testcase-sheet';
|
|
24
25
|
|
|
25
26
|
const MANUAL_SHEET = 'Manual';
|
|
@@ -169,6 +170,70 @@ function resetNote(prior: CarriedCell[]): ExcelJS.CellValue {
|
|
|
169
170
|
return own ? `${header}\n${own}` : header;
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
/**
|
|
174
|
+
* What happens to each Manual variant's prior row: `carry` its cells, or `reset` it to Pending
|
|
175
|
+
* because the procedure changed since the result was recorded. A row with nothing recorded has
|
|
176
|
+
* no entry. The ONE decision both the model (totals, #891) and the workbook cells read.
|
|
177
|
+
*/
|
|
178
|
+
export function planManualCarry(model: MatrixModel, prior: PriorManualResults | null): Map<string, 'carry' | 'reset'> {
|
|
179
|
+
const plan = new Map<string, 'carry' | 'reset'>();
|
|
180
|
+
if (!prior) return plan;
|
|
181
|
+
for (const v of model.items.flatMap((i) => i.variants)) {
|
|
182
|
+
if (v.mode !== 'manual') continue;
|
|
183
|
+
const before = prior.rows.get(v.ref);
|
|
184
|
+
if (!before || isUntouched(before.cells)) continue;
|
|
185
|
+
const stamped = prior.stamps?.get(v.ref);
|
|
186
|
+
// Only a recorded result is a claim about the procedure. A row with no result (a
|
|
187
|
+
// blocker note, or one already reset) is carried as-is, so a second procedure
|
|
188
|
+
// change does not stack a second marker on it.
|
|
189
|
+
const hasResult = !['', 'Pending'].includes(cellText(before.cells[0].value));
|
|
190
|
+
plan.set(v.ref, stamped !== undefined && stamped !== v.fingerprint && hasResult ? 'reset' : 'carry');
|
|
191
|
+
}
|
|
192
|
+
return plan;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A typed Result → its state word, case-insensitively (Excel's COUNTIF ignores case too). */
|
|
196
|
+
const STATE_BY_LOWER = new Map<string, VariantState>(
|
|
197
|
+
(['Passed', 'Failed', 'Blocked', 'Pending', 'N/A'] as VariantState[]).map((s) => [s.toLowerCase(), s]),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Put the carried Manual results into the MODEL, before anything is rendered or counted.
|
|
202
|
+
*
|
|
203
|
+
* The workbook's totals, Overview and group roll-ups are formulas with a cached result computed
|
|
204
|
+
* from the model, and the CSV and the CLI summary read the model too. Carrying only the cells
|
|
205
|
+
* after rendering left all of them counting the recorded Manual results as Pending: a preview
|
|
206
|
+
* of the file, and the CLI, reported "5 Pending" for cases the tester had run (#891).
|
|
207
|
+
* A reset row counts as Pending, as its cell reads. A Result that is not a state word (a tester's
|
|
208
|
+
* "OK") counts as Pending too; the cell keeps the text, and its date still reaches the CSV.
|
|
209
|
+
* Idempotent: every Manual variant's carried fields are cleared first, so the result depends only
|
|
210
|
+
* on `prior`.
|
|
211
|
+
*/
|
|
212
|
+
export function applyManualCarry(model: MatrixModel, prior: PriorManualResults | null): void {
|
|
213
|
+
const plan = planManualCarry(model, prior);
|
|
214
|
+
for (const item of model.items) {
|
|
215
|
+
let touched = false;
|
|
216
|
+
for (const v of item.variants) {
|
|
217
|
+
if (v.mode !== 'manual') continue;
|
|
218
|
+
if (v.manualState !== undefined || v.manualDate !== undefined) touched = true;
|
|
219
|
+
delete v.manualState;
|
|
220
|
+
delete v.manualDate;
|
|
221
|
+
const decision = plan.get(v.ref);
|
|
222
|
+
if (!decision) continue;
|
|
223
|
+
touched = true;
|
|
224
|
+
if (decision === 'reset') { v.manualState = 'Pending'; continue; }
|
|
225
|
+
const [result, date] = prior!.rows.get(v.ref)!.cells.map((c) => cellText(c.value));
|
|
226
|
+
if (date) v.manualDate = date;
|
|
227
|
+
v.manualState = STATE_BY_LOWER.get(result.toLowerCase()) ?? 'Pending';
|
|
228
|
+
}
|
|
229
|
+
if (touched) {
|
|
230
|
+
const { result, counts } = rollUp(item.variants);
|
|
231
|
+
item.result = result;
|
|
232
|
+
item.resultCounts = counts;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
172
237
|
/**
|
|
173
238
|
* Carry `prior` into the freshly rendered `wb`, then stamp every manual variant's fingerprint.
|
|
174
239
|
* Only the Manual sheet's variant sub-rows are touched.
|
|
@@ -178,6 +243,7 @@ export function carryManualResults(
|
|
|
178
243
|
model: MatrixModel,
|
|
179
244
|
prior: PriorManualResults | null,
|
|
180
245
|
): ManualCarryReport {
|
|
246
|
+
const plan = planManualCarry(model, prior);
|
|
181
247
|
const report: ManualCarryReport = { carried: 0, unchecked: 0, reset: [], notPlaced: [], duplicates: prior?.duplicates ?? [] };
|
|
182
248
|
const fingerprints = new Map(
|
|
183
249
|
model.items.flatMap((i) => i.variants).filter((v) => v.mode === 'manual').map((v) => [v.ref, v.fingerprint]),
|
|
@@ -190,14 +256,11 @@ export function carryManualResults(
|
|
|
190
256
|
const before = prior.rows.get(ref);
|
|
191
257
|
if (!before) continue;
|
|
192
258
|
placed.add(ref);
|
|
193
|
-
|
|
259
|
+
const decision = plan.get(ref);
|
|
260
|
+
if (!decision) continue;
|
|
194
261
|
|
|
195
262
|
const stamped = prior.stamps?.get(ref);
|
|
196
|
-
|
|
197
|
-
// blocker note, or one already reset) is carried as-is, so a second procedure
|
|
198
|
-
// change does not stack a second marker on it.
|
|
199
|
-
const hasResult = !['', 'Pending'].includes(cellText(before.cells[0].value));
|
|
200
|
-
if (stamped !== undefined && stamped !== fingerprints.get(ref) && hasResult) {
|
|
263
|
+
if (decision === 'reset') {
|
|
201
264
|
row.getCell(RESULT_COL).value = 'Pending';
|
|
202
265
|
row.getCell(CARRIED_COLS[NOTE_INDEX]).value = resetNote(before.cells);
|
|
203
266
|
report.reset.push(ref);
|