Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
ygopro-cardtest-web
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
ygopro-cardtest-web
Commits
d7af8fce
Commit
d7af8fce
authored
Feb 01, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
filters
parent
30f1c126
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
647 additions
and
96 deletions
+647
-96
src/app/app.css
src/app/app.css
+30
-0
src/app/app.html
src/app/app.html
+193
-68
src/app/app.ts
src/app/app.ts
+408
-28
src/styles.css
src/styles.css
+16
-0
No files found.
src/app/app.css
View file @
d7af8fce
...
@@ -9,3 +9,33 @@
...
@@ -9,3 +9,33 @@
.progress
{
.progress
{
height
:
0.75rem
;
height
:
0.75rem
;
}
}
.report-scroll
{
max-height
:
70vh
;
overflow-y
:
auto
;
padding-right
:
0.5rem
;
}
details
>
summary
{
cursor
:
pointer
;
list-style
:
none
;
}
details
>
summary
::-webkit-details-marker
{
display
:
none
;
}
.summary-row
{
user-select
:
none
;
}
details
>
summary
::after
{
content
:
"▾"
;
color
:
#6c757d
;
margin-left
:
0.5rem
;
transition
:
transform
0.15s
ease
;
}
details
[
open
]
>
summary
::after
{
transform
:
rotate
(
180deg
);
}
src/app/app.html
View file @
d7af8fce
...
@@ -7,23 +7,35 @@
...
@@ -7,23 +7,35 @@
</p>
</p>
</header>
</header>
<section
class=
"card shadow-sm mb-4"
>
<section
class=
"card shadow-sm mb-4
no-print
"
>
<div
class=
"card-body"
>
<div
class=
"card-body"
>
<div
class=
"mb-3"
>
<div
class=
"mb-3"
>
<label
class=
"form-label"
for=
"packageInput"
>
选择扩展包
</label>
<div
class=
"d-flex align-items-center gap-2"
>
<input
<div
class=
"flex-grow-1"
>
id=
"packageInput"
<label
class=
"form-label"
for=
"packageInput"
>
选择扩展包
</label>
class=
"form-control"
<input
type=
"file"
id=
"packageInput"
accept=
".zip,.ypk,application/zip"
class=
"form-control"
multiple
type=
"file"
[disabled]=
"running()"
accept=
".zip,.ypk,application/zip"
(click)=
"onFileInputClick($event)"
multiple
(change)=
"onFileSelection($event)"
[disabled]=
"running()"
aria-describedby=
"packageHelp"
(click)=
"onFileInputClick($event)"
/>
(change)=
"onFileSelection($event)"
<div
id=
"packageHelp"
class=
"form-text"
>
aria-describedby=
"packageHelp"
支持多选,支持 zip 或 ypk。
/>
<div
id=
"packageHelp"
class=
"form-text"
>
支持多选,支持 zip 或 ypk。
</div>
</div>
<button
type=
"button"
class=
"btn btn-outline-secondary btn-sm"
[disabled]=
"running()"
(click)=
"clearSelection()"
>
清空
</button>
</div>
</div>
</div>
</div>
...
@@ -34,20 +46,15 @@
...
@@ -34,20 +46,15 @@
@for (file of selectedFiles(); track file.name) {
@for (file of selectedFiles(); track file.name) {
<li
class=
"list-group-item px-0 d-flex justify-content-between"
>
<li
class=
"list-group-item px-0 d-flex justify-content-between"
>
<span>
{{ file.name }}
</span>
<span>
{{ file.name }}
</span>
<span
class=
"text-muted small"
>
{{ f
ile.size }} bytes
</span>
<span
class=
"text-muted small"
>
{{ f
ormatFileSize(file.size) }}
</span>
</li>
</li>
}
}
</ul>
</ul>
</div>
</div>
}
}
<div
class=
"d-flex flex-wrap gap-2"
>
<div
class=
"small text-muted"
>
<button
type=
"button"
class=
"btn btn-primary"
[disabled]=
"!canStart()"
(click)=
"startTests()"
>
选择文件后将自动开始测试。
开始测试
</button>
<button
type=
"button"
class=
"btn btn-outline-secondary"
[disabled]=
"running()"
(click)=
"clearSelection()"
>
清空
</button>
</div>
</div>
</div>
</div>
</section>
</section>
...
@@ -58,7 +65,7 @@
...
@@ -58,7 +65,7 @@
</div>
</div>
}
}
<section
class=
"card shadow-sm mb-4"
[attr.aria-busy]=
"running()"
>
<section
class=
"card shadow-sm mb-4
no-print
"
[attr.aria-busy]=
"running()"
>
<div
class=
"card-body"
>
<div
class=
"card-body"
>
<div
class=
"d-flex flex-wrap justify-content-between align-items-center gap-3 mb-2"
>
<div
class=
"d-flex flex-wrap justify-content-between align-items-center gap-3 mb-2"
>
<div>
<div>
...
@@ -96,44 +103,124 @@
...
@@ -96,44 +103,124 @@
@if (hasResults()) {
@if (hasResults()) {
<section
class=
"mb-5"
aria-live=
"polite"
>
<section
class=
"mb-5"
aria-live=
"polite"
>
<h2
class=
"h5 mb-3"
>
测试报告
</h2>
<div
class=
"d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2"
>
<h2
class=
"h5 mb-0"
>
测试报告
</h2>
@for (report of reports(); track report.fileName) {
<div
class=
"d-flex flex-wrap align-items-center gap-2 no-print"
>
<article
class=
"card shadow-sm mb-4"
>
@if (zipFilterOptions().length > 1) {
<div
class=
"card-header d-flex flex-wrap justify-content-between align-items-center gap-2"
>
<label
class=
"form-label small mb-0"
for=
"zipFilter"
>
扩展包筛选
</label>
<div>
<select
<h3
class=
"h6 mb-1"
>
{{ report.fileName }}
</h3>
id=
"zipFilter"
<div
class=
"small text-muted"
>
{{ packageCardSummary(report) }}
</div>
class=
"form-select form-select-sm w-auto"
[value]=
"zipFilter()"
(change)=
"onZipFilterChange($event)"
>
<option
value=
"all"
>
全部
</option>
@if (zipFilterOptions().includes('passed')) {
<option
value=
"passed"
>
正常
</option>
}
@if (zipFilterOptions().includes('failed')) {
<option
value=
"failed"
>
异常
</option>
}
@if (zipFilterOptions().includes('empty')) {
<option
value=
"empty"
>
无内容
</option>
}
</select>
}
<button
type=
"button"
class=
"btn btn-outline-secondary btn-sm"
(click)=
"exportPdf()"
>
导出 PDF
</button>
</div>
</div>
<div
class=
"card shadow-sm mb-3"
>
<div
class=
"card-body py-2"
>
<div
class=
"row g-2 small"
>
<div
class=
"col-md-4"
>
扩展包:{{ overallStats().zipTotal }},正常 {{ overallStats().zipPassed }},异常
{{ overallStats().zipFailed }}
</div>
<div
class=
"col-md-4"
>
CDB:{{ overallStats().cdbTotal }},正常 {{ overallStats().cdbPassed }},异常
{{ overallStats().cdbFailed }}
</div>
<div
class=
"col-md-4"
>
卡片:{{ overallStats().cardTotal }},通过 {{ overallStats().cardPassed }},失败
{{ overallStats().cardFailed }}
</div>
</div>
<span
[class]=
"packageBadgeClass(report.status)"
>
{{ packageStatusLabel(report.status) }}
</span>
</div>
</div>
<div
class=
"card-body"
>
</div>
</div>
<div
class=
"report-scroll"
tabindex=
"0"
aria-label=
"测试报告滚动区域"
>
@if (visibleReports().length === 0) {
<div
class=
"text-muted"
>
当前筛选无扩展包。
</div>
}
@for (report of visibleReports(); track report.fileName) {
<details
class=
"card shadow-sm mb-4"
open
>
<summary
class=
"card-header summary-row d-flex flex-wrap align-items-center gap-2"
>
<div>
<h3
class=
"h6 mb-1"
>
{{ report.fileName }}
</h3>
<div
class=
"small text-muted"
>
{{ packageCardSummary(report) }} · {{ packageCdbSummary(report) }}
</div>
</div>
<span
class=
"ms-auto"
[class]=
"packageBadgeClass(report)"
>
{{ packageStatusLabel(report) }}
</span>
</summary>
<div
class=
"card-body"
>
@if (report.error) {
@if (report.error) {
<div
class=
"alert alert-danger"
role=
"alert"
>
<div
class=
"alert alert-danger"
role=
"alert"
>
{{ report.error }}
{{ report.error }}
</div>
</div>
}
}
@if (cdbFilterOptions(report).length > 1) {
<div
class=
"d-flex flex-wrap align-items-center gap-2 mb-3"
>
<label
class=
"form-label small mb-0"
for=
"cdbFilter-{{ packageIndexOf(report) }}"
>
CDB 筛选
</label>
<select
id=
"cdbFilter-{{ packageIndexOf(report) }}"
class=
"form-select form-select-sm w-auto"
[value]=
"report.cdbFilter"
(change)=
"onPackageFilterChange($event, report)"
>
<option
value=
"all"
>
全部
</option>
@if (cdbFilterOptions(report).includes('passed')) {
<option
value=
"passed"
>
正常
</option>
}
@if (cdbFilterOptions(report).includes('failed')) {
<option
value=
"failed"
>
异常
</option>
}
@if (cdbFilterOptions(report).includes('empty')) {
<option
value=
"empty"
>
无内容
</option>
}
</select>
</div>
}
@if (report.cdbs.length === 0) {
@if (report.cdbs.length === 0) {
<div
class=
"text-muted"
>
未在根目录找到 cdb 文件。
</div>
<div
class=
"text-muted"
>
未在根目录找到 cdb 文件。
</div>
} @else if (visibleCdbs(report).length === 0) {
<div
class=
"text-muted"
>
当前筛选无 cdb。
</div>
}
}
@for (cdb of
report.cdbs
; track cdb.fileName) {
@for (cdb of
visibleCdbs(report)
; track cdb.fileName) {
<
section
class=
"border rounded p-3 mb-3"
>
<
details
class=
"border rounded p-3 mb-3"
open
>
<
div
class=
"d-flex flex-wrap justify-content-between
align-items-start gap-2"
>
<
summary
class=
"summary-row d-flex flex-wrap
align-items-start gap-2"
>
<div>
<div>
<h4
class=
"h6 mb-1"
>
{{ cdb.fileName }}
</h4>
<h4
class=
"h6 mb-1"
>
{{ cdb.fileName }}
</h4>
<div
class=
"small text-muted"
>
<div
class=
"small text-muted"
>
卡片 {{ cdb.totalCards }},已测 {{ cdb.testedCards }},通过 {{ cdb.passedCards }},失败
卡片 {{ cdb.total
Testable
Cards }},已测 {{ cdb.testedCards }},通过 {{ cdb.passedCards }},失败
{{ cdb.failedCards }}
{{ cdb.failedCards }}
</div>
</div>
</div>
</div>
<span
[class]=
"cdbBadgeClass(cdb.status
)"
>
<span
class=
"ms-auto"
[class]=
"cdbBadgeClass(cdb
)"
>
{{ cdbStatusLabel(cdb
.status
) }}
{{ cdbStatusLabel(cdb) }}
</span>
</span>
</
div
>
</
summary
>
@if (cdb.error) {
@if (cdb.error) {
<div
class=
"alert alert-danger mt-2"
role=
"alert"
>
<div
class=
"alert alert-danger mt-2"
role=
"alert"
>
...
@@ -141,11 +228,44 @@
...
@@ -141,11 +228,44 @@
</div>
</div>
}
}
@if (cardFilterOptions(cdb).length > 1) {
<div
class=
"d-flex flex-wrap align-items-center gap-2 mt-2"
>
<label
class=
"form-label small mb-0"
for=
"cardFilter-{{ packageIndexOf(report) }}-{{ cdbIndexOf(report, cdb) }}"
>
卡片筛选
</label>
<select
id=
"cardFilter-{{ packageIndexOf(report) }}-{{ cdbIndexOf(report, cdb) }}"
class=
"form-select form-select-sm w-auto"
[value]=
"cdb.cardFilter"
(change)=
"onCdbFilterChange($event, report, cdb)"
>
<option
value=
"all"
>
全部
</option>
@if (cardFilterOptions(cdb).includes('passed')) {
<option
value=
"passed"
>
通过
</option>
}
@if (cardFilterOptions(cdb).includes('failed')) {
<option
value=
"failed"
>
失败
</option>
}
@if (cardFilterOptions(cdb).includes('skip')) {
<option
value=
"skip"
>
无需测试
</option>
}
@if (cardFilterOptions(cdb).includes('empty')) {
<option
value=
"empty"
>
无内容
</option>
}
</select>
</div>
}
@if (cdb.cards.length === 0) {
@if (cdb.cards.length === 0) {
<div
class=
"text-muted mt-2"
>
没有需要测试的卡片。
</div>
<div
class=
"text-muted mt-2"
>
没有需要测试的卡片。
</div>
} @else if (visibleCards(cdb).length === 0) {
<div
class=
"text-muted mt-2"
>
当前筛选无卡片。
</div>
}
}
@for (card of
cdb.cards
; track card.id) {
@for (card of
visibleCards(cdb)
; track card.id) {
<div
class=
"card mt-3"
>
<div
class=
"card mt-3"
>
<div
class=
"card-body py-2"
>
<div
class=
"card-body py-2"
>
<div
class=
"d-flex flex-wrap justify-content-between align-items-start gap-2"
>
<div
class=
"d-flex flex-wrap justify-content-between align-items-start gap-2"
>
...
@@ -154,12 +274,22 @@
...
@@ -154,12 +274,22 @@
{{ card.name }}
{{ card.name }}
<span
class=
"text-muted"
>
#{{ card.id }}
</span>
<span
class=
"text-muted"
>
#{{ card.id }}
</span>
</div>
</div>
<div
class=
"small text-muted"
>
@if (card.skipTest) {
ScriptError 数量:{{ card.scriptErrors.length }}
<div
class=
"small text-muted"
>
无需测试
</div>
</div>
} @else if (card.scriptErrors.length > 0) {
<div
class=
"small text-muted"
>
错误信息数量:{{ card.scriptErrors.length }}
</div>
} @else if (card.status === 'passed') {
<div
class=
"small text-muted"
>
卡片状态正常
</div>
} @else if (card.status === 'pending') {
<div
class=
"small text-muted"
>
等待测试输出。
</div>
} @else if (card.status === 'running') {
<div
class=
"small text-muted"
>
正在生成测试输出...
</div>
}
</div>
</div>
<span
[class]=
"cardBadgeClass
(card.status
)"
>
<span
[class]=
"cardBadgeClass
ForCard(card
)"
>
{{ cardStatusLabel
(card.status
) }}
{{ cardStatusLabel
ForCard(card
) }}
</span>
</span>
</div>
</div>
...
@@ -170,13 +300,9 @@
...
@@ -170,13 +300,9 @@
</div>
</div>
}
}
<div
class=
"mt-2"
>
@if (card.logs.length > 0) {
<div
class=
"small text-muted"
>
测试输出
</div>
<div
class=
"mt-2"
>
@if (card.status === 'pending') {
<div
class=
"small text-muted"
>
测试输出
</div>
<div
class=
"small text-muted"
>
等待测试输出。
</div>
} @else if (card.status === 'running') {
<div
class=
"small text-muted"
>
正在生成测试输出...
</div>
} @else if (card.logs.length > 0) {
<ul
class=
"small mb-0"
>
<ul
class=
"small mb-0"
>
@for (entry of card.logs; track $index) {
@for (entry of card.logs; track $index) {
<li
<li
...
@@ -187,23 +313,22 @@
...
@@ -187,23 +313,22 @@
</li>
</li>
}
}
</ul>
</ul>
} @else if (card.status === 'error') {
</div>
<div
class=
"small text-muted"
>
测试未生成日志输出。
</div>
}
} @else {
<div
class=
"small text-muted"
>
卡片状态正常
</div>
}
</div>
</div>
</div>
</div>
</div>
}
}
</
section
>
</
details
>
}
}
</div>
</div>
</article>
</details>
}
}
</div>
</section>
</section>
} @else {
<div
class=
"text-muted"
>
暂无测试报告。
</div>
}
}
</div>
</div>
<footer
class=
"py-4 text-center text-muted small"
>
<span>
© 2026 Nanahira
</span>
</footer>
</main>
</main>
src/app/app.ts
View file @
d7af8fce
...
@@ -20,8 +20,16 @@ import {
...
@@ -20,8 +20,16 @@ import {
import
{
YGOPRO_CDB_URL
,
YGOPRO_SCRIPT_ZIP_URL
}
from
'
./resource-urls
'
;
import
{
YGOPRO_CDB_URL
,
YGOPRO_SCRIPT_ZIP_URL
}
from
'
./resource-urls
'
;
type
PackageStatus
=
'
pending
'
|
'
running
'
|
'
done
'
|
'
error
'
;
type
PackageStatus
=
'
pending
'
|
'
running
'
|
'
done
'
|
'
error
'
;
type
CdbStatus
=
'
pending
'
|
'
running
'
|
'
done
'
|
'
skipped
'
|
'
error
'
;
type
CdbStatus
=
'
pending
'
|
'
running
'
|
'
done
'
|
'
error
'
;
type
CardStatus
=
'
pending
'
|
'
running
'
|
'
passed
'
|
'
failed
'
|
'
error
'
;
type
CardStatus
=
'
pending
'
|
'
running
'
|
'
passed
'
|
'
failed
'
|
'
error
'
;
type
FilterOption
=
'
all
'
|
'
passed
'
|
'
failed
'
|
'
skip
'
|
'
empty
'
;
type
DisplayStatus
=
|
'
pending
'
|
'
running
'
|
'
normal
'
|
'
abnormal
'
|
'
error
'
|
'
empty
'
;
type
CardTestResult
=
{
type
CardTestResult
=
{
id
:
number
;
id
:
number
;
...
@@ -29,13 +37,16 @@ type CardTestResult = {
...
@@ -29,13 +37,16 @@ type CardTestResult = {
status
:
CardStatus
;
status
:
CardStatus
;
scriptErrors
:
string
[];
scriptErrors
:
string
[];
logs
:
TestCardMessage
[];
logs
:
TestCardMessage
[];
skipTest
:
boolean
;
detail
?:
string
;
detail
?:
string
;
};
};
type
CdbTestResult
=
{
type
CdbTestResult
=
{
fileName
:
string
;
fileName
:
string
;
status
:
CdbStatus
;
status
:
CdbStatus
;
cardFilter
:
FilterOption
;
cards
:
CardTestResult
[];
cards
:
CardTestResult
[];
totalTestableCards
:
number
;
totalCards
:
number
;
totalCards
:
number
;
testedCards
:
number
;
testedCards
:
number
;
passedCards
:
number
;
passedCards
:
number
;
...
@@ -46,6 +57,7 @@ type CdbTestResult = {
...
@@ -46,6 +57,7 @@ type CdbTestResult = {
type
PackageTestResult
=
{
type
PackageTestResult
=
{
fileName
:
string
;
fileName
:
string
;
status
:
PackageStatus
;
status
:
PackageStatus
;
cdbFilter
:
FilterOption
;
cdbs
:
CdbTestResult
[];
cdbs
:
CdbTestResult
[];
error
?:
string
;
error
?:
string
;
};
};
...
@@ -60,6 +72,7 @@ type ProgressSnapshot = {
...
@@ -60,6 +72,7 @@ type ProgressSnapshot = {
type
SqlCardRow
=
{
type
SqlCardRow
=
{
id
:
number
;
id
:
number
;
name
:
string
|
null
;
name
:
string
|
null
;
type
:
number
|
null
;
};
};
type
ZipCdbEntry
=
{
type
ZipCdbEntry
=
{
...
@@ -93,7 +106,7 @@ export class App {
...
@@ -93,7 +106,7 @@ export class App {
let
failed
=
0
;
let
failed
=
0
;
for
(
const
report
of
this
.
reports
())
{
for
(
const
report
of
this
.
reports
())
{
for
(
const
cdb
of
report
.
cdbs
)
{
for
(
const
cdb
of
report
.
cdbs
)
{
total
+=
cdb
.
totalCards
;
total
+=
cdb
.
total
Testable
Cards
;
done
+=
cdb
.
testedCards
;
done
+=
cdb
.
testedCards
;
failed
+=
cdb
.
failedCards
;
failed
+=
cdb
.
failedCards
;
}
}
...
@@ -111,6 +124,61 @@ export class App {
...
@@ -111,6 +124,61 @@ export class App {
});
});
protected
readonly
hasResults
=
computed
(()
=>
this
.
reports
().
length
>
0
);
protected
readonly
hasResults
=
computed
(()
=>
this
.
reports
().
length
>
0
);
protected
readonly
zipFilter
=
signal
<
FilterOption
>
(
'
all
'
);
protected
readonly
visibleReports
=
computed
(()
=>
this
.
reports
().
filter
((
report
)
=>
this
.
matchesFilter
(
this
.
packageDisplayStatus
(
report
),
this
.
resolveFilter
(
this
.
zipFilter
(),
this
.
zipFilterOptions
()),
),
),
);
protected
readonly
overallStats
=
computed
(()
=>
{
const
reports
=
this
.
reports
();
const
zipTotal
=
reports
.
length
;
let
zipPassed
=
0
;
let
zipFailed
=
0
;
let
cdbTotal
=
0
;
let
cdbPassed
=
0
;
let
cdbFailed
=
0
;
let
cardTotal
=
0
;
let
cardPassed
=
0
;
let
cardFailed
=
0
;
for
(
const
report
of
reports
)
{
const
zipStatus
=
this
.
packageDisplayStatus
(
report
);
if
(
zipStatus
===
'
normal
'
)
{
zipPassed
+=
1
;
}
else
if
(
zipStatus
===
'
abnormal
'
||
zipStatus
===
'
error
'
)
{
zipFailed
+=
1
;
}
for
(
const
cdb
of
report
.
cdbs
)
{
cdbTotal
+=
1
;
const
cdbStatus
=
this
.
cdbDisplayStatus
(
cdb
);
if
(
cdbStatus
===
'
normal
'
)
{
cdbPassed
+=
1
;
}
else
if
(
cdbStatus
===
'
abnormal
'
||
cdbStatus
===
'
error
'
)
{
cdbFailed
+=
1
;
}
cardTotal
+=
cdb
.
totalTestableCards
;
cardPassed
+=
cdb
.
passedCards
;
cardFailed
+=
cdb
.
failedCards
;
}
}
return
{
zipTotal
,
zipPassed
,
zipFailed
,
cdbTotal
,
cdbPassed
,
cdbFailed
,
cardTotal
,
cardPassed
,
cardFailed
,
};
});
private
sqlModule
:
SqlJsStatic
|
null
=
null
;
private
sqlModule
:
SqlJsStatic
|
null
=
null
;
private
baseDb
:
Database
|
null
=
null
;
private
baseDb
:
Database
|
null
=
null
;
...
@@ -130,10 +198,12 @@ export class App {
...
@@ -130,10 +198,12 @@ export class App {
this
.
selectedFiles
.
set
(
files
);
this
.
selectedFiles
.
set
(
files
);
this
.
reports
.
set
([]);
this
.
reports
.
set
([]);
this
.
globalError
.
set
(
null
);
this
.
globalError
.
set
(
null
);
this
.
zipFilter
.
set
(
'
all
'
);
if
(
files
.
length
===
0
)
{
if
(
files
.
length
===
0
)
{
this
.
statusMessage
.
set
(
'
等待上传扩展包。
'
);
this
.
statusMessage
.
set
(
'
等待上传扩展包。
'
);
}
else
{
}
else
{
this
.
statusMessage
.
set
(
`已选择
${
files
.
length
}
个扩展包,准备开始测试。`
);
this
.
statusMessage
.
set
(
`已选择
${
files
.
length
}
个扩展包,开始测试。`
);
void
this
.
startTests
();
}
}
}
}
...
@@ -141,6 +211,7 @@ export class App {
...
@@ -141,6 +211,7 @@ export class App {
this
.
selectedFiles
.
set
([]);
this
.
selectedFiles
.
set
([]);
this
.
reports
.
set
([]);
this
.
reports
.
set
([]);
this
.
globalError
.
set
(
null
);
this
.
globalError
.
set
(
null
);
this
.
zipFilter
.
set
(
'
all
'
);
this
.
statusMessage
.
set
(
'
已清空选择。
'
);
this
.
statusMessage
.
set
(
'
已清空选择。
'
);
}
}
...
@@ -156,6 +227,7 @@ export class App {
...
@@ -156,6 +227,7 @@ export class App {
files
.
map
((
file
)
=>
({
files
.
map
((
file
)
=>
({
fileName
:
file
.
name
,
fileName
:
file
.
name
,
status
:
'
pending
'
,
status
:
'
pending
'
,
cdbFilter
:
'
all
'
,
cdbs
:
[],
cdbs
:
[],
})),
})),
);
);
...
@@ -184,12 +256,16 @@ export class App {
...
@@ -184,12 +256,16 @@ export class App {
}
}
}
}
protected
packageBadgeClass
(
status
:
PackageStatus
):
string
{
protected
packageBadgeClass
(
report
:
PackageTestResult
):
string
{
switch
(
status
)
{
switch
(
this
.
packageDisplayStatus
(
report
)
)
{
case
'
running
'
:
case
'
running
'
:
return
'
badge text-bg-primary
'
;
return
'
badge text-bg-primary
'
;
case
'
done
'
:
case
'
normal
'
:
return
'
badge text-bg-success
'
;
return
'
badge text-bg-success
'
;
case
'
abnormal
'
:
return
'
badge text-bg-warning
'
;
case
'
empty
'
:
return
'
badge text-bg-secondary
'
;
case
'
error
'
:
case
'
error
'
:
return
'
badge text-bg-danger
'
;
return
'
badge text-bg-danger
'
;
default
:
default
:
...
@@ -197,12 +273,16 @@ export class App {
...
@@ -197,12 +273,16 @@ export class App {
}
}
}
}
protected
packageStatusLabel
(
status
:
PackageStatus
):
string
{
protected
packageStatusLabel
(
report
:
PackageTestResult
):
string
{
switch
(
status
)
{
switch
(
this
.
packageDisplayStatus
(
report
)
)
{
case
'
running
'
:
case
'
running
'
:
return
'
测试中
'
;
return
'
测试中
'
;
case
'
done
'
:
case
'
normal
'
:
return
'
已完成
'
;
return
'
正常
'
;
case
'
abnormal
'
:
return
'
异常
'
;
case
'
empty
'
:
return
'
无内容
'
;
case
'
error
'
:
case
'
error
'
:
return
'
出错
'
;
return
'
出错
'
;
default
:
default
:
...
@@ -210,14 +290,16 @@ export class App {
...
@@ -210,14 +290,16 @@ export class App {
}
}
}
}
protected
cdbBadgeClass
(
status
:
CdbStatus
):
string
{
protected
cdbBadgeClass
(
cdb
:
CdbTestResult
):
string
{
switch
(
status
)
{
switch
(
this
.
cdbDisplayStatus
(
cdb
)
)
{
case
'
running
'
:
case
'
running
'
:
return
'
badge text-bg-primary
'
;
return
'
badge text-bg-primary
'
;
case
'
done
'
:
case
'
normal
'
:
return
'
badge text-bg-success
'
;
return
'
badge text-bg-success
'
;
case
'
skipped
'
:
case
'
abnormal
'
:
return
'
badge text-bg-warning
'
;
return
'
badge text-bg-warning
'
;
case
'
empty
'
:
return
'
badge text-bg-secondary
'
;
case
'
error
'
:
case
'
error
'
:
return
'
badge text-bg-danger
'
;
return
'
badge text-bg-danger
'
;
default
:
default
:
...
@@ -225,14 +307,16 @@ export class App {
...
@@ -225,14 +307,16 @@ export class App {
}
}
}
}
protected
cdbStatusLabel
(
status
:
CdbStatus
):
string
{
protected
cdbStatusLabel
(
cdb
:
CdbTestResult
):
string
{
switch
(
status
)
{
switch
(
this
.
cdbDisplayStatus
(
cdb
)
)
{
case
'
running
'
:
case
'
running
'
:
return
'
测试中
'
;
return
'
测试中
'
;
case
'
done
'
:
case
'
normal
'
:
return
'
已完成
'
;
return
'
正常
'
;
case
'
skipped
'
:
case
'
abnormal
'
:
return
'
跳过
'
;
return
'
异常
'
;
case
'
empty
'
:
return
'
无内容
'
;
case
'
error
'
:
case
'
error
'
:
return
'
出错
'
;
return
'
出错
'
;
default
:
default
:
...
@@ -269,12 +353,134 @@ export class App {
...
@@ -269,12 +353,134 @@ export class App {
}
}
}
}
protected
cardBadgeClassForCard
(
card
:
CardTestResult
):
string
{
if
(
card
.
skipTest
)
{
return
'
badge text-bg-secondary
'
;
}
return
this
.
cardBadgeClass
(
card
.
status
);
}
protected
cardStatusLabelForCard
(
card
:
CardTestResult
):
string
{
if
(
card
.
skipTest
)
{
return
'
无需测试
'
;
}
return
this
.
cardStatusLabel
(
card
.
status
);
}
protected
visibleCdbs
(
report
:
PackageTestResult
):
CdbTestResult
[]
{
const
filter
=
this
.
resolveFilter
(
report
.
cdbFilter
,
this
.
cdbFilterOptions
(
report
));
return
report
.
cdbs
.
filter
((
cdb
)
=>
this
.
matchesFilter
(
this
.
cdbDisplayStatus
(
cdb
),
filter
),
);
}
protected
visibleCards
(
cdb
:
CdbTestResult
):
CardTestResult
[]
{
const
filter
=
this
.
resolveFilter
(
cdb
.
cardFilter
,
this
.
cardFilterOptions
(
cdb
));
return
cdb
.
cards
.
filter
((
card
)
=>
this
.
matchesFilter
(
this
.
cardDisplayStatus
(
card
),
filter
),
);
}
protected
zipFilterOptions
():
FilterOption
[]
{
return
this
.
filterOptionsForStatuses
(
this
.
reports
().
map
((
report
)
=>
this
.
packageDisplayStatus
(
report
)),
);
}
protected
cdbFilterOptions
(
report
:
PackageTestResult
):
FilterOption
[]
{
return
this
.
filterOptionsForStatuses
(
report
.
cdbs
.
map
((
cdb
)
=>
this
.
cdbDisplayStatus
(
cdb
)),
);
}
protected
cardFilterOptions
(
cdb
:
CdbTestResult
):
FilterOption
[]
{
return
this
.
filterOptionsForStatuses
(
cdb
.
cards
.
map
((
card
)
=>
this
.
cardDisplayStatus
(
card
)),
);
}
protected
onZipFilterChange
(
event
:
Event
):
void
{
const
target
=
event
.
target
as
HTMLSelectElement
|
null
;
const
value
=
target
?.
value
??
'
all
'
;
if
(
!
this
.
isFilterOption
(
value
))
{
return
;
}
this
.
zipFilter
.
set
(
value
);
}
protected
onPackageFilterChange
(
event
:
Event
,
report
:
PackageTestResult
,
):
void
{
const
target
=
event
.
target
as
HTMLSelectElement
|
null
;
const
value
=
target
?.
value
??
'
all
'
;
if
(
!
this
.
isFilterOption
(
value
))
{
return
;
}
const
packageIndex
=
this
.
packageIndexOf
(
report
);
if
(
packageIndex
<
0
)
{
return
;
}
this
.
updatePackage
(
packageIndex
,
(
current
)
=>
({
...
current
,
cdbFilter
:
value
,
}));
}
protected
onCdbFilterChange
(
event
:
Event
,
report
:
PackageTestResult
,
cdb
:
CdbTestResult
,
):
void
{
const
target
=
event
.
target
as
HTMLSelectElement
|
null
;
const
value
=
target
?.
value
??
'
all
'
;
if
(
!
this
.
isFilterOption
(
value
))
{
return
;
}
const
packageIndex
=
this
.
packageIndexOf
(
report
);
if
(
packageIndex
<
0
)
{
return
;
}
const
cdbIndex
=
this
.
cdbIndexOf
(
report
,
cdb
);
if
(
cdbIndex
<
0
)
{
return
;
}
this
.
updateCdb
(
packageIndex
,
cdbIndex
,
(
current
)
=>
({
...
current
,
cardFilter
:
value
,
}));
}
protected
packageIndexOf
(
report
:
PackageTestResult
):
number
{
return
this
.
reports
().
indexOf
(
report
);
}
protected
cdbIndexOf
(
report
:
PackageTestResult
,
cdb
:
CdbTestResult
):
number
{
return
report
.
cdbs
.
indexOf
(
cdb
);
}
protected
formatFileSize
(
bytes
:
number
):
string
{
if
(
bytes
>=
1024
*
1024
)
{
return
`
${(
bytes
/
(
1024
*
1024
)).
toFixed
(
1
)}
MB`
;
}
if
(
bytes
>=
1024
)
{
return
`
${
Math
.
round
(
bytes
/
1024
)}
KB`
;
}
return
`
${
bytes
}
bytes`
;
}
protected
exportPdf
():
void
{
if
(
typeof
window
!==
'
undefined
'
)
{
window
.
print
();
}
}
protected
packageCardSummary
(
report
:
PackageTestResult
):
string
{
protected
packageCardSummary
(
report
:
PackageTestResult
):
string
{
let
total
=
0
;
let
total
=
0
;
let
passed
=
0
;
let
passed
=
0
;
let
failed
=
0
;
let
failed
=
0
;
for
(
const
cdb
of
report
.
cdbs
)
{
for
(
const
cdb
of
report
.
cdbs
)
{
total
+=
cdb
.
totalCards
;
total
+=
cdb
.
total
Testable
Cards
;
passed
+=
cdb
.
passedCards
;
passed
+=
cdb
.
passedCards
;
failed
+=
cdb
.
failedCards
;
failed
+=
cdb
.
failedCards
;
}
}
...
@@ -284,6 +490,23 @@ export class App {
...
@@ -284,6 +490,23 @@ export class App {
return
`卡片
${
total
}
,通过
${
passed
}
,失败
${
failed
}
`
;
return
`卡片
${
total
}
,通过
${
passed
}
,失败
${
failed
}
`
;
}
}
protected
packageCdbSummary
(
report
:
PackageTestResult
):
string
{
if
(
report
.
cdbs
.
length
===
0
)
{
return
'
CDB 0
'
;
}
let
passed
=
0
;
let
failed
=
0
;
for
(
const
cdb
of
report
.
cdbs
)
{
const
status
=
this
.
cdbDisplayStatus
(
cdb
);
if
(
status
===
'
normal
'
)
{
passed
+=
1
;
}
else
if
(
status
===
'
abnormal
'
||
status
===
'
error
'
)
{
failed
+=
1
;
}
}
return
`CDB
${
report
.
cdbs
.
length
}
,正常
${
passed
}
,异常
${
failed
}
`
;
}
private
async
testPackage
(
file
:
File
,
packageIndex
:
number
):
Promise
<
void
>
{
private
async
testPackage
(
file
:
File
,
packageIndex
:
number
):
Promise
<
void
>
{
this
.
updatePackage
(
packageIndex
,
(
report
)
=>
({
this
.
updatePackage
(
packageIndex
,
(
report
)
=>
({
...
report
,
...
report
,
...
@@ -296,7 +519,9 @@ export class App {
...
@@ -296,7 +519,9 @@ export class App {
const
buffer
=
await
file
.
arrayBuffer
();
const
buffer
=
await
file
.
arrayBuffer
();
const
zip
=
await
JSZip
.
loadAsync
(
buffer
);
const
zip
=
await
JSZip
.
loadAsync
(
buffer
);
const
cdbEntries
=
this
.
extractRootCdbEntries
(
zip
);
const
cdbEntries
=
this
.
extractRootCdbEntries
(
zip
);
const
cdbReports
=
cdbEntries
.
map
((
entry
)
=>
this
.
createCdbReport
(
entry
.
displayName
));
const
cdbReports
=
cdbEntries
.
map
((
entry
)
=>
this
.
createCdbReport
(
entry
.
displayName
),
);
this
.
updatePackage
(
packageIndex
,
(
report
)
=>
({
this
.
updatePackage
(
packageIndex
,
(
report
)
=>
({
...
report
,
...
report
,
...
@@ -330,7 +555,8 @@ export class App {
...
@@ -330,7 +555,8 @@ export class App {
}
}
const
snapshot
=
this
.
reports
()[
packageIndex
];
const
snapshot
=
this
.
reports
()[
packageIndex
];
const
hasError
=
snapshot
?.
cdbs
.
some
((
cdb
)
=>
cdb
.
status
===
'
error
'
)
??
false
;
const
hasError
=
snapshot
?.
cdbs
.
some
((
cdb
)
=>
cdb
.
status
===
'
error
'
)
??
false
;
this
.
updatePackage
(
packageIndex
,
(
report
)
=>
({
this
.
updatePackage
(
packageIndex
,
(
report
)
=>
({
...
report
,
...
report
,
status
:
hasError
?
'
error
'
:
'
done
'
,
status
:
hasError
?
'
error
'
:
'
done
'
,
...
@@ -387,7 +613,8 @@ export class App {
...
@@ -387,7 +613,8 @@ export class App {
if
(
cards
.
length
===
0
)
{
if
(
cards
.
length
===
0
)
{
this
.
updateCdb
(
packageIndex
,
cdbIndex
,
(
cdb
)
=>
({
this
.
updateCdb
(
packageIndex
,
cdbIndex
,
(
cdb
)
=>
({
...
cdb
,
...
cdb
,
status
:
'
skipped
'
,
status
:
'
done
'
,
totalTestableCards
:
0
,
totalCards
:
0
,
totalCards
:
0
,
testedCards
:
0
,
testedCards
:
0
,
passedCards
:
0
,
passedCards
:
0
,
...
@@ -403,12 +630,15 @@ export class App {
...
@@ -403,12 +630,15 @@ export class App {
status
:
'
pending
'
,
status
:
'
pending
'
,
scriptErrors
:
[],
scriptErrors
:
[],
logs
:
[],
logs
:
[],
skipTest
:
card
.
skipTest
,
}));
}));
const
testableCards
=
cardResults
.
filter
((
card
)
=>
!
card
.
skipTest
);
this
.
updateCdb
(
packageIndex
,
cdbIndex
,
(
cdb
)
=>
({
this
.
updateCdb
(
packageIndex
,
cdbIndex
,
(
cdb
)
=>
({
...
cdb
,
...
cdb
,
cards
:
cardResults
,
cards
:
cardResults
,
totalCards
:
cardResults
.
length
,
totalCards
:
cardResults
.
length
,
totalTestableCards
:
testableCards
.
length
,
testedCards
:
0
,
testedCards
:
0
,
passedCards
:
0
,
passedCards
:
0
,
failedCards
:
0
,
failedCards
:
0
,
...
@@ -416,6 +646,9 @@ export class App {
...
@@ -416,6 +646,9 @@ export class App {
for
(
let
cardIndex
=
0
;
cardIndex
<
cardResults
.
length
;
cardIndex
+=
1
)
{
for
(
let
cardIndex
=
0
;
cardIndex
<
cardResults
.
length
;
cardIndex
+=
1
)
{
const
card
=
cardResults
[
cardIndex
];
const
card
=
cardResults
[
cardIndex
];
if
(
card
.
skipTest
)
{
continue
;
}
this
.
statusMessage
.
set
(
`正在测试
${
entry
.
displayName
}
-
${
card
.
name
}
`
);
this
.
statusMessage
.
set
(
`正在测试
${
entry
.
displayName
}
-
${
card
.
name
}
`
);
this
.
updateCard
(
packageIndex
,
cdbIndex
,
cardIndex
,
(
item
)
=>
({
this
.
updateCard
(
packageIndex
,
cdbIndex
,
cardIndex
,
(
item
)
=>
({
...
item
,
...
item
,
...
@@ -479,7 +712,9 @@ export class App {
...
@@ -479,7 +712,9 @@ export class App {
return
{
return
{
fileName
,
fileName
,
status
:
'
pending
'
,
status
:
'
pending
'
,
cardFilter
:
'
all
'
,
cards
:
[],
cards
:
[],
totalTestableCards
:
0
,
totalCards
:
0
,
totalCards
:
0
,
testedCards
:
0
,
testedCards
:
0
,
passedCards
:
0
,
passedCards
:
0
,
...
@@ -580,6 +815,7 @@ export class App {
...
@@ -580,6 +815,7 @@ export class App {
status
:
scriptErrors
.
length
===
0
?
'
passed
'
:
'
failed
'
,
status
:
scriptErrors
.
length
===
0
?
'
passed
'
:
'
failed
'
,
scriptErrors
,
scriptErrors
,
logs
,
logs
,
skipTest
:
false
,
};
};
}
catch
(
error
)
{
}
catch
(
error
)
{
return
{
return
{
...
@@ -588,6 +824,7 @@ export class App {
...
@@ -588,6 +824,7 @@ export class App {
status
:
'
error
'
,
status
:
'
error
'
,
scriptErrors
:
[],
scriptErrors
:
[],
logs
:
[],
logs
:
[],
skipTest
:
false
,
detail
:
this
.
formatErrorStack
(
error
),
detail
:
this
.
formatErrorStack
(
error
),
};
};
}
finally
{
}
finally
{
...
@@ -595,25 +832,28 @@ export class App {
...
@@ -595,25 +832,28 @@ export class App {
}
}
}
}
private
queryTestCards
(
db
:
Database
):
Array
<
{
id
:
number
;
name
:
string
}
>
{
private
queryTestCards
(
db
:
Database
):
Array
<
{
id
:
number
;
name
:
string
;
skipTest
:
boolean
}
>
{
const
normalMonsterType
=
const
normalMonsterType
=
(
OcgcoreCommonConstants
.
TYPE_NORMAL
|
OcgcoreCommonConstants
.
TYPE_MONSTER
)
>>>
0
;
(
OcgcoreCommonConstants
.
TYPE_NORMAL
|
OcgcoreCommonConstants
.
TYPE_MONSTER
)
>>>
0
;
const
tokenType
=
OcgcoreCommonConstants
.
TYPE_TOKEN
>>>
0
;
const
tokenType
=
OcgcoreCommonConstants
.
TYPE_TOKEN
>>>
0
;
const
stmt
=
db
.
prepare
(
const
stmt
=
db
.
prepare
(
'
SELECT datas.id as id, texts.name as name
FROM datas INNER JOIN texts ON datas.id = texts.id WHERE datas.type != ? AND (datas.type & ?) = 0
'
,
'
SELECT datas.id as id, texts.name as name
, datas.type as type FROM datas INNER JOIN texts ON datas.id = texts.id
'
,
);
);
try
{
try
{
const
result
:
Array
<
{
id
:
number
;
name
:
string
}
>
=
[];
const
result
:
Array
<
{
id
:
number
;
name
:
string
;
skipTest
:
boolean
}
>
=
[];
stmt
.
bind
([
normalMonsterType
,
tokenType
]);
while
(
stmt
.
step
())
{
while
(
stmt
.
step
())
{
const
row
=
stmt
.
getAsObject
()
as
unknown
as
SqlCardRow
;
const
row
=
stmt
.
getAsObject
()
as
unknown
as
SqlCardRow
;
if
(
!
row
||
row
.
id
==
null
)
{
if
(
!
row
||
row
.
id
==
null
)
{
continue
;
continue
;
}
}
const
typeValue
=
(
row
.
type
??
0
)
>>>
0
;
const
skipTest
=
typeValue
===
normalMonsterType
||
(
typeValue
&
tokenType
)
!==
0
;
result
.
push
({
result
.
push
({
id
:
row
.
id
,
id
:
row
.
id
,
name
:
row
.
name
??
'
未命名卡片
'
,
name
:
row
.
name
??
'
未命名卡片
'
,
skipTest
,
});
});
}
}
return
result
;
return
result
;
...
@@ -719,6 +959,146 @@ export class App {
...
@@ -719,6 +959,146 @@ export class App {
return
status
===
'
failed
'
||
status
===
'
error
'
;
return
status
===
'
failed
'
||
status
===
'
error
'
;
}
}
private
cardDisplayStatus
(
card
:
CardTestResult
,
):
'
passed
'
|
'
failed
'
|
'
pending
'
|
'
skip
'
{
if
(
card
.
skipTest
)
{
return
'
skip
'
;
}
if
(
card
.
status
===
'
passed
'
)
{
return
'
passed
'
;
}
if
(
card
.
status
===
'
failed
'
||
card
.
status
===
'
error
'
)
{
return
'
failed
'
;
}
return
'
pending
'
;
}
private
cdbDisplayStatus
(
cdb
:
CdbTestResult
):
DisplayStatus
{
if
(
cdb
.
status
===
'
error
'
)
{
return
'
error
'
;
}
if
(
cdb
.
status
===
'
running
'
)
{
return
'
running
'
;
}
if
(
cdb
.
totalTestableCards
===
0
&&
cdb
.
status
===
'
done
'
)
{
return
'
empty
'
;
}
if
(
cdb
.
failedCards
>
0
)
{
return
'
abnormal
'
;
}
if
(
cdb
.
totalTestableCards
>
0
&&
cdb
.
testedCards
===
cdb
.
totalTestableCards
)
{
return
'
normal
'
;
}
return
'
pending
'
;
}
private
packageDisplayStatus
(
report
:
PackageTestResult
):
DisplayStatus
{
if
(
report
.
status
===
'
error
'
)
{
return
'
error
'
;
}
if
(
report
.
status
===
'
running
'
)
{
return
'
running
'
;
}
if
(
report
.
status
===
'
done
'
&&
report
.
cdbs
.
length
===
0
)
{
return
'
empty
'
;
}
if
(
report
.
status
===
'
done
'
&&
report
.
cdbs
.
length
>
0
&&
report
.
cdbs
.
every
((
cdb
)
=>
this
.
cdbDisplayStatus
(
cdb
)
===
'
empty
'
)
)
{
return
'
empty
'
;
}
const
hasFailed
=
report
.
cdbs
.
some
((
cdb
)
=>
this
.
cdbDisplayStatus
(
cdb
)
===
'
abnormal
'
)
||
report
.
cdbs
.
some
((
cdb
)
=>
this
.
cdbDisplayStatus
(
cdb
)
===
'
error
'
);
if
(
hasFailed
)
{
return
'
abnormal
'
;
}
const
allDone
=
report
.
cdbs
.
every
((
cdb
)
=>
{
const
status
=
this
.
cdbDisplayStatus
(
cdb
);
return
status
===
'
normal
'
||
status
===
'
empty
'
||
status
===
'
error
'
;
});
if
(
allDone
&&
report
.
cdbs
.
length
>
0
)
{
return
'
normal
'
;
}
return
'
pending
'
;
}
private
matchesFilter
(
status
:
'
passed
'
|
'
failed
'
|
DisplayStatus
|
'
skip
'
,
filter
:
FilterOption
,
):
boolean
{
if
(
filter
===
'
all
'
)
{
return
true
;
}
if
(
filter
===
'
passed
'
)
{
return
status
===
'
passed
'
||
status
===
'
normal
'
;
}
if
(
filter
===
'
skip
'
)
{
return
status
===
'
skip
'
;
}
if
(
filter
===
'
empty
'
)
{
return
status
===
'
empty
'
;
}
return
status
===
'
failed
'
||
status
===
'
abnormal
'
||
status
===
'
error
'
;
}
private
filterOptionsForStatuses
(
statuses
:
Array
<
'
passed
'
|
'
failed
'
|
DisplayStatus
|
'
pending
'
|
'
skip
'
>
,
):
FilterOption
[]
{
let
hasPassed
=
false
;
let
hasFailed
=
false
;
let
hasSkip
=
false
;
let
hasEmpty
=
false
;
for
(
const
status
of
statuses
)
{
if
(
status
===
'
passed
'
||
status
===
'
normal
'
)
{
hasPassed
=
true
;
}
else
if
(
status
===
'
failed
'
||
status
===
'
abnormal
'
||
status
===
'
error
'
)
{
hasFailed
=
true
;
}
else
if
(
status
===
'
skip
'
)
{
hasSkip
=
true
;
}
else
if
(
status
===
'
empty
'
)
{
hasEmpty
=
true
;
}
}
const
options
:
FilterOption
[]
=
[];
if
(
hasPassed
)
{
options
.
push
(
'
passed
'
);
}
if
(
hasFailed
)
{
options
.
push
(
'
failed
'
);
}
if
(
hasSkip
)
{
options
.
push
(
'
skip
'
);
}
if
(
hasEmpty
)
{
options
.
push
(
'
empty
'
);
}
return
options
;
}
private
resolveFilter
(
current
:
FilterOption
,
options
:
FilterOption
[],
):
FilterOption
{
if
(
options
.
includes
(
current
))
{
return
current
;
}
return
'
all
'
;
}
private
isFilterOption
(
value
:
string
):
value
is
FilterOption
{
return
(
value
===
'
all
'
||
value
===
'
passed
'
||
value
===
'
failed
'
||
value
===
'
skip
'
||
value
===
'
empty
'
);
}
private
formatError
(
error
:
unknown
):
string
{
private
formatError
(
error
:
unknown
):
string
{
if
(
error
instanceof
Error
)
{
if
(
error
instanceof
Error
)
{
return
error
.
message
;
return
error
.
message
;
...
...
src/styles.css
View file @
d7af8fce
...
@@ -11,3 +11,19 @@ body {
...
@@ -11,3 +11,19 @@ body {
main
{
main
{
min-height
:
100vh
;
min-height
:
100vh
;
}
}
@media
print
{
body
{
background
:
#ffffff
;
}
.no-print
{
display
:
none
!important
;
}
.report-scroll
{
max-height
:
none
!important
;
overflow
:
visible
!important
;
padding-right
:
0
!important
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment