kempo-testing-framework 1.1.0
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/.github/copilot-instructions.md +105 -0
- package/CONTRIBUTING.md +107 -0
- package/README.md +293 -0
- package/gui/components/Collapsible.js +54 -0
- package/gui/components/Icon.js +151 -0
- package/gui/components/Logs.js +73 -0
- package/gui/components/SettingCheckbox.js +42 -0
- package/gui/components/SettingNumber.js +77 -0
- package/gui/components/SettingSelect.js +67 -0
- package/gui/components/Test.js +99 -0
- package/gui/components/TestFramework.js +236 -0
- package/gui/components/TestSuite.js +181 -0
- package/gui/components/TestSummary.js +189 -0
- package/gui/components/Theme.js +40 -0
- package/gui/components/settingsStore.js +46 -0
- package/gui/icons/fail.svg +1 -0
- package/gui/icons/logs.svg +1 -0
- package/gui/icons/pass.svg +1 -0
- package/gui/icons/play.svg +1 -0
- package/gui/icons/running.svg +1 -0
- package/gui/icons/scheduled.svg +1 -0
- package/gui/icons/settings.svg +1 -0
- package/gui/icons/theme-auto.svg +1 -0
- package/gui/icons/theme-dark.svg +1 -0
- package/gui/icons/theme-light.svg +1 -0
- package/gui/index.html +108 -0
- package/gui/lit-all.min.js +120 -0
- package/index.js +122 -0
- package/package.json +21 -0
- package/src/browserTestServer.js +115 -0
- package/src/cli.js +198 -0
- package/src/findTests.js +34 -0
- package/src/gui.js +249 -0
- package/src/runBrowserTests.js +71 -0
- package/src/runTestFiles.js +94 -0
- package/src/runTests.js +83 -0
- package/src/utils/logLevels.js +7 -0
- package/test.html +23 -0
- package/tests/Counter.js +34 -0
- package/tests/cli-flags.node-test.js +54 -0
- package/tests/cli-loglevel.node-test.js +40 -0
- package/tests/collapsible.browser-test.js +49 -0
- package/tests/counter.browser-test.js +141 -0
- package/tests/example.node-test.js +103 -0
- package/tests/icon.browser-test.js +54 -0
- package/tests/logs.browser-test.js +47 -0
- package/tests/setting-checkbox.browser-test.js +48 -0
- package/tests/setting-number.browser-test.js +54 -0
- package/tests/setting-select.browser-test.js +47 -0
- package/tests/settings-store.browser-test.js +26 -0
- package/tests/src-browserTestServer.node-test.js +47 -0
- package/tests/src-cli.node-test.js +32 -0
- package/tests/src-findTests.node-test.js +41 -0
- package/tests/src-logLevels.node-test.js +29 -0
- package/tests/src-runBrowserTests.node-test.js +41 -0
- package/tests/src-runTestFiles.node-test.js +42 -0
- package/tests/src-runTests.node-test.js +56 -0
- package/tests/test-framework.browser-test.js +65 -0
- package/tests/test-summary.browser-test.js +78 -0
- package/tests/test.browser-test.js +56 -0
- package/tests/testfile.browser-test.js +60 -0
- package/tests/theme.browser-test.js +38 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { LitElement, html, css, render } from '../lit-all.min.js';
|
|
2
|
+
import './Icon.js';
|
|
3
|
+
import './Test.js';
|
|
4
|
+
import './Logs.js';
|
|
5
|
+
import './Collapsible.js';
|
|
6
|
+
import { subscribe } from './settingsStore.js';
|
|
7
|
+
|
|
8
|
+
export const statusMap = {
|
|
9
|
+
notran: 'Not Ran',
|
|
10
|
+
queued: 'Queued',
|
|
11
|
+
running: 'Running',
|
|
12
|
+
pass: 'Pass',
|
|
13
|
+
fail: 'Fail'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
class TestSuiteEl extends LitElement {
|
|
17
|
+
/*
|
|
18
|
+
Properties
|
|
19
|
+
*/
|
|
20
|
+
static properties = {
|
|
21
|
+
file: { type: String, reflect: true },
|
|
22
|
+
testNames: { type: Array },
|
|
23
|
+
status: { type: String, reflect: true }
|
|
24
|
+
};
|
|
25
|
+
#unsubscribe = null;
|
|
26
|
+
#testsContainer = null;
|
|
27
|
+
constructor(){
|
|
28
|
+
super();
|
|
29
|
+
this.file = '';
|
|
30
|
+
this.testNames = [];
|
|
31
|
+
this.status = 'notran';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
LifecycleCallbacks
|
|
36
|
+
*/
|
|
37
|
+
connectedCallback(){
|
|
38
|
+
super.connectedCallback();
|
|
39
|
+
}
|
|
40
|
+
firstUpdated(){
|
|
41
|
+
// Listen on host so light-DOM children bubble here
|
|
42
|
+
this.addEventListener('test_status_change', this.testStatusChangeHandler);
|
|
43
|
+
this.#unsubscribe = subscribe(() => this.requestUpdate());
|
|
44
|
+
// Ensure a light-DOM container exists for tests and render them there
|
|
45
|
+
const existing = this.querySelector('[slot="tests"]');
|
|
46
|
+
this.#testsContainer = existing || (() => {
|
|
47
|
+
const c = document.createElement('div');
|
|
48
|
+
c.setAttribute('slot', 'tests');
|
|
49
|
+
this.appendChild(c);
|
|
50
|
+
return c;
|
|
51
|
+
})();
|
|
52
|
+
this.renderTests();
|
|
53
|
+
}
|
|
54
|
+
disconnectedCallback(){
|
|
55
|
+
super.disconnectedCallback();
|
|
56
|
+
this.removeEventListener('test_status_change', this.testStatusChangeHandler);
|
|
57
|
+
if(this.#unsubscribe) this.#unsubscribe();
|
|
58
|
+
}
|
|
59
|
+
updated(changedProps){
|
|
60
|
+
if(changedProps.has('status')){
|
|
61
|
+
this.dispatchEvent(new CustomEvent('testfile_status_change', {
|
|
62
|
+
detail: { file: this.file, status: this.status },
|
|
63
|
+
bubbles: true,
|
|
64
|
+
composed: true
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
if(changedProps.has('file') || changedProps.has('testNames')){
|
|
68
|
+
this.renderTests();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
Methods
|
|
74
|
+
*/
|
|
75
|
+
runAllTests = () => {
|
|
76
|
+
const getFrameworkEl = () => {
|
|
77
|
+
let node = this;
|
|
78
|
+
while(node){
|
|
79
|
+
if(node && node.closest){
|
|
80
|
+
const fw = node.closest('ktf-test-framework');
|
|
81
|
+
if(fw) return fw;
|
|
82
|
+
}
|
|
83
|
+
const root = node?.getRootNode?.();
|
|
84
|
+
const host = root && root.host ? root.host : null;
|
|
85
|
+
if(!host) break;
|
|
86
|
+
node = host;
|
|
87
|
+
}
|
|
88
|
+
return document.querySelector('ktf-test-framework');
|
|
89
|
+
};
|
|
90
|
+
const fw = getFrameworkEl();
|
|
91
|
+
if(fw && typeof fw.enqueueSuite==='function') fw.enqueueSuite({ file: this.file, testNames: this.testNames, el: this });
|
|
92
|
+
};
|
|
93
|
+
testStatusChangeHandler = () => { this.status = this.calcStatus(); };
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
Utility Methods
|
|
97
|
+
*/
|
|
98
|
+
getFrameworkEl = () => {
|
|
99
|
+
let node = this;
|
|
100
|
+
while(node){
|
|
101
|
+
if(node && node.closest){
|
|
102
|
+
const fw = node.closest('ktf-test-framework');
|
|
103
|
+
if(fw) return fw;
|
|
104
|
+
}
|
|
105
|
+
const root = node?.getRootNode?.();
|
|
106
|
+
const host = root && root.host ? root.host : null;
|
|
107
|
+
if(!host) break;
|
|
108
|
+
node = host;
|
|
109
|
+
}
|
|
110
|
+
return document.querySelector('ktf-test-framework');
|
|
111
|
+
};
|
|
112
|
+
calcStatus = () => {
|
|
113
|
+
const root = this.#testsContainer || this;
|
|
114
|
+
const testElements = root.querySelectorAll('ktf-test');
|
|
115
|
+
const statuses = Array.from(testElements).map(el => el.status);
|
|
116
|
+
if(statuses.includes('running')) return 'running';
|
|
117
|
+
if(statuses.includes('fail')) return 'fail';
|
|
118
|
+
if(statuses.length && statuses.every(s => s==='pass')) return 'pass';
|
|
119
|
+
return 'notran';
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
Light DOM rendering for tests
|
|
124
|
+
*/
|
|
125
|
+
renderTests(){
|
|
126
|
+
if(!this.#testsContainer) return;
|
|
127
|
+
const list = Array.isArray(this.testNames) ? this.testNames : [];
|
|
128
|
+
const tpl = html`${list.map(name => html`<ktf-test .file=${this.file} .name=${name}></ktf-test>`)}`;
|
|
129
|
+
render(tpl, this.#testsContainer);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
Rendering
|
|
134
|
+
*/
|
|
135
|
+
render(){
|
|
136
|
+
if(!this.file || !this.testNames.length) return html``;
|
|
137
|
+
const titleText = this.file.replace('tests/', '').replace('.node-test.js', '').replace('.browser-test.js', '').replace('.test.js', '');
|
|
138
|
+
return html`
|
|
139
|
+
<link rel="stylesheet" href="/essential.css">
|
|
140
|
+
<ktf-collapsible>
|
|
141
|
+
<span slot="title" id="title" class="-ml">${titleText}</span>
|
|
142
|
+
<div slot="actions">
|
|
143
|
+
${this.status==='notran' || this.status==='queued' ? html`
|
|
144
|
+
<button class="no-btn d-ib ph" @click=${this.runAllTests} aria-label="Run Test Suite" ?disabled=${this.status==='queued'}>
|
|
145
|
+
<ktf-icon name="${this.status==='queued'?'scheduled':'play'}"></ktf-icon>
|
|
146
|
+
</button>
|
|
147
|
+
` : html`
|
|
148
|
+
<span class="d-ib ph status-color" aria-hidden="true">
|
|
149
|
+
<ktf-icon name="${this.status}" animation="${this.status==='running'?'spin':'none'}"></ktf-icon>
|
|
150
|
+
</span>
|
|
151
|
+
`}
|
|
152
|
+
</div>
|
|
153
|
+
<div id="details">
|
|
154
|
+
<div id="status">
|
|
155
|
+
<h6>${statusMap[this.status]}</h6>
|
|
156
|
+
${this.status!=='running'?html`
|
|
157
|
+
<button class="primary mb" @click=${this.runAllTests} ?disabled=${this.status==='queued'}>Run Test Suite</button>
|
|
158
|
+
`:html``}
|
|
159
|
+
</div>
|
|
160
|
+
<ktf-logs id="fileLogs"></ktf-logs>
|
|
161
|
+
<slot name="tests"></slot>
|
|
162
|
+
</div>
|
|
163
|
+
</ktf-collapsible>
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static styles = css`
|
|
168
|
+
:host{ --tf_status: var(--tc_default, inherit); }
|
|
169
|
+
:host([status="running"]){ --tf_status: var(--tc_primary, #3366ff); }
|
|
170
|
+
:host([status="pass"]){ --tf_status: var(--tc_success, rgb(0, 136, 0)); }
|
|
171
|
+
:host([status="fail"]){ --tf_status: var(--tc_danger, rgb(255, 0, 51)); }
|
|
172
|
+
#title{ font-size: 1.25rem; font-weight: 600; }
|
|
173
|
+
div[slot="actions"]{ font-size: 1.25rem; }
|
|
174
|
+
#title,
|
|
175
|
+
#status{ color: var(--tf_status); }
|
|
176
|
+
.status-color{ color: var(--tf_status); }
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
window.customElements.define('ktf-test-suite', TestSuiteEl);
|
|
181
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { LitElement, html, css } from '../lit-all.min.js';
|
|
2
|
+
import './Collapsible.js';
|
|
3
|
+
import './Icon.js';
|
|
4
|
+
|
|
5
|
+
window.customElements.define('ktf-test-summary', class extends LitElement {
|
|
6
|
+
/*
|
|
7
|
+
Properties
|
|
8
|
+
*/
|
|
9
|
+
static properties = {
|
|
10
|
+
status: { type: String, reflect: true },
|
|
11
|
+
fileCounts: { state: true },
|
|
12
|
+
testCounts: { state: true },
|
|
13
|
+
queueLength: { state: true }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
constructor(){
|
|
17
|
+
super();
|
|
18
|
+
this.status = 'notran';
|
|
19
|
+
this.testsMap = new Map();
|
|
20
|
+
this.fileCounts = { total: 0, pass: 0, fail: 0, running: 0, notran: 0 };
|
|
21
|
+
this.testCounts = { total: 0, pass: 0, fail: 0, running: 0, notran: 0 };
|
|
22
|
+
this.queueLength = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Lifecycle Callbacks
|
|
27
|
+
*/
|
|
28
|
+
connectedCallback(){
|
|
29
|
+
super.connectedCallback();
|
|
30
|
+
const fw = this.getFrameworkEl();
|
|
31
|
+
(fw||window).addEventListener('testfile_status_change', this.onFileStatusChange);
|
|
32
|
+
(fw||window).addEventListener('test_status_change', this.onTestStatusChange);
|
|
33
|
+
if(fw) fw.addEventListener('ktf:queue-updated', this.onQueueUpdated);
|
|
34
|
+
}
|
|
35
|
+
disconnectedCallback(){
|
|
36
|
+
super.disconnectedCallback();
|
|
37
|
+
const fw = this.getFrameworkEl();
|
|
38
|
+
(fw||window).removeEventListener('testfile_status_change', this.onFileStatusChange);
|
|
39
|
+
(fw||window).removeEventListener('test_status_change', this.onTestStatusChange);
|
|
40
|
+
if(fw) fw.removeEventListener('ktf:queue-updated', this.onQueueUpdated);
|
|
41
|
+
}
|
|
42
|
+
firstUpdated(){
|
|
43
|
+
this.initializeFromChildren();
|
|
44
|
+
const fw = this.getFrameworkEl();
|
|
45
|
+
if(fw){ this.onQueueUpdated({ detail: { length: fw.queue?.length || 0 } }); }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
Event Handlers
|
|
50
|
+
*/
|
|
51
|
+
onSlotChange = () => { this.initializeFromChildren(); };
|
|
52
|
+
onFileStatusChange = () => { this.recountFiles(); this.updateSuiteStatus(); };
|
|
53
|
+
onTestStatusChange = (e) => {
|
|
54
|
+
const d = e?.detail || {};
|
|
55
|
+
if(d && d.file && d.name && d.status){
|
|
56
|
+
const key = `${d.file}::${d.name}`;
|
|
57
|
+
this.testsMap.set(key, d.status);
|
|
58
|
+
this.recountTests();
|
|
59
|
+
this.updateSuiteStatus();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
onQueueUpdated = (e) => { this.queueLength = Number(e?.detail?.length||0); };
|
|
63
|
+
getFrameworkEl(){ return this.closest('ktf-test-framework'); }
|
|
64
|
+
runAllSuites = () => {
|
|
65
|
+
const fw = this.getFrameworkEl();
|
|
66
|
+
if(fw && typeof fw.runAllSuites==='function') fw.runAllSuites();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
Utility Methods
|
|
71
|
+
*/
|
|
72
|
+
initializeFromChildren(){
|
|
73
|
+
const root = this.getFrameworkEl() || document;
|
|
74
|
+
const fileEls = Array.from(root.querySelectorAll('ktf-test-suite'));
|
|
75
|
+
for(const el of fileEls){
|
|
76
|
+
const file = el.file || el.getAttribute('file') || '';
|
|
77
|
+
const names = Array.isArray(el.testNames) ? el.testNames : [];
|
|
78
|
+
for(const name of names){
|
|
79
|
+
const key = `${file}::${name}`;
|
|
80
|
+
if(!this.testsMap.has(key)) this.testsMap.set(key, 'notran');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this.recountFiles();
|
|
84
|
+
this.recountTests();
|
|
85
|
+
this.updateSuiteStatus();
|
|
86
|
+
}
|
|
87
|
+
recountFiles(){
|
|
88
|
+
const root = this.getFrameworkEl() || document;
|
|
89
|
+
const fileEls = Array.from(root.querySelectorAll('ktf-test-suite'));
|
|
90
|
+
const counts = { total: fileEls.length, pass: 0, fail: 0, running: 0, notran: 0 };
|
|
91
|
+
for(const el of fileEls){
|
|
92
|
+
const s = el.getAttribute('status') || 'notran';
|
|
93
|
+
if(s==='pass') counts.pass++;
|
|
94
|
+
else if(s==='fail') counts.fail++;
|
|
95
|
+
else if(s==='running') counts.running++;
|
|
96
|
+
else counts.notran++;
|
|
97
|
+
}
|
|
98
|
+
this.fileCounts = counts;
|
|
99
|
+
}
|
|
100
|
+
recountTests(){
|
|
101
|
+
const counts = { total: 0, pass: 0, fail: 0, running: 0, notran: 0 };
|
|
102
|
+
for(const s of this.testsMap.values()){
|
|
103
|
+
counts.total++;
|
|
104
|
+
if(s==='pass') counts.pass++;
|
|
105
|
+
else if(s==='fail') counts.fail++;
|
|
106
|
+
else if(s==='running') counts.running++;
|
|
107
|
+
else counts.notran++;
|
|
108
|
+
}
|
|
109
|
+
this.testCounts = counts;
|
|
110
|
+
}
|
|
111
|
+
updateSuiteStatus(){
|
|
112
|
+
const f = this.fileCounts;
|
|
113
|
+
const t = this.testCounts;
|
|
114
|
+
let next = 'notran';
|
|
115
|
+
if(f.running>0 || t.running>0) next = 'running';
|
|
116
|
+
else if(f.fail>0 || t.fail>0) next = 'fail';
|
|
117
|
+
else if((f.total>0 || t.total>0) && f.fail===0 && t.fail===0 && f.running===0 && t.running===0 && f.notran===0 && t.notran===0) next = 'pass';
|
|
118
|
+
else next = 'notran';
|
|
119
|
+
if(this.status!==next) this.status = next;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
Rendering
|
|
124
|
+
*/
|
|
125
|
+
render(){
|
|
126
|
+
const f = this.fileCounts;
|
|
127
|
+
const t = this.testCounts;
|
|
128
|
+
return html`
|
|
129
|
+
<link rel="stylesheet" href="/essential.css">
|
|
130
|
+
<ktf-collapsible opened>
|
|
131
|
+
<span slot="title" class="-ml"><b>Test Summary</b></span>
|
|
132
|
+
<div slot="actions">
|
|
133
|
+
${this.status==='notran' ? html`
|
|
134
|
+
<button class="no-btn d-ib ph" @click=${this.runAllSuites} aria-label="Run All Tests">
|
|
135
|
+
<ktf-icon name="play"></ktf-icon>
|
|
136
|
+
</button>
|
|
137
|
+
` : html`
|
|
138
|
+
<span class="d-ib ph status-color" aria-hidden="true">
|
|
139
|
+
<ktf-icon name="${this.status}" animation="${this.status==='running'?'spin':'none'}"></ktf-icon>
|
|
140
|
+
</span>
|
|
141
|
+
`}
|
|
142
|
+
</div>
|
|
143
|
+
<div class="summary mb">
|
|
144
|
+
<button class="primary mb" @click=${this.runAllSuites} ?disabled=${this.status==='running'}>Run All Tests</button>
|
|
145
|
+
<div class="counts mt">
|
|
146
|
+
<span class="muted">Queue: ${this.queueLength}</span>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="row">
|
|
149
|
+
<div class="col">
|
|
150
|
+
<h6>Files</h6>
|
|
151
|
+
<div class="counts">
|
|
152
|
+
<span>Total: ${f.total}</span>
|
|
153
|
+
<span class="pass">Pass: ${f.pass}</span>
|
|
154
|
+
<span class="fail">Fail: ${f.fail}</span>
|
|
155
|
+
<span class="running">Running: ${f.running}</span>
|
|
156
|
+
<span class="notran">Not Ran: ${f.notran}</span>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="col">
|
|
160
|
+
<h6>Tests</h6>
|
|
161
|
+
<div class="counts">
|
|
162
|
+
<span>Total: ${t.total}</span>
|
|
163
|
+
<span class="pass">Pass: ${t.pass}</span>
|
|
164
|
+
<span class="fail">Fail: ${t.fail}</span>
|
|
165
|
+
<span class="running">Running: ${t.running}</span>
|
|
166
|
+
<span class="notran">Not Ran: ${t.notran}</span>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
<slot></slot>
|
|
171
|
+
</div>
|
|
172
|
+
</ktf-collapsible>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static styles = css`
|
|
177
|
+
:host{ --suite_status: var(--tc_default, inherit); }
|
|
178
|
+
:host([status="running"]){ --suite_status: var(--tc_primary,#3366ff); }
|
|
179
|
+
:host([status="pass"]){ --suite_status: var(--tc_success, rgb(0,136,0)); }
|
|
180
|
+
:host([status="fail"]){ --suite_status: var(--tc_danger, rgb(255,0,51)); }
|
|
181
|
+
.summary h6{ color: var(--suite_status); }
|
|
182
|
+
div[slot="actions"]{ font-size: 1.25rem; }
|
|
183
|
+
.counts{ display:flex; gap: var(--spacer_h); flex-wrap: wrap; }
|
|
184
|
+
.counts .pass{ color: var(--tc_success, rgb(0,136,0)); }
|
|
185
|
+
.counts .fail{ color: var(--tc_danger, rgb(255,0,51)); }
|
|
186
|
+
.counts .running{ color: var(--tc_primary,#3366ff); }
|
|
187
|
+
.counts .notran{ color: var(--tc_muted); }
|
|
188
|
+
`;
|
|
189
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { LitElement, html, css } from '../lit-all.min.js';
|
|
2
|
+
import { getSettings, subscribe } from './settingsStore.js';
|
|
3
|
+
|
|
4
|
+
window.customElements.define('ktf-theme', class extends LitElement {
|
|
5
|
+
static properties = {
|
|
6
|
+
theme: { type: String, reflect: true }
|
|
7
|
+
}
|
|
8
|
+
#unsubscribe = null;
|
|
9
|
+
constructor(){
|
|
10
|
+
super();
|
|
11
|
+
this.theme = (getSettings().theme) || 'auto';
|
|
12
|
+
}
|
|
13
|
+
connectedCallback(){
|
|
14
|
+
super.connectedCallback();
|
|
15
|
+
this.applyTheme(this.theme);
|
|
16
|
+
this.#unsubscribe = subscribe(s => {
|
|
17
|
+
if (s.theme !== this.theme) {
|
|
18
|
+
this.theme = s.theme || 'auto';
|
|
19
|
+
this.applyTheme(this.theme);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
disconnectedCallback(){
|
|
24
|
+
super.disconnectedCallback();
|
|
25
|
+
if (this.#unsubscribe) this.#unsubscribe();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
applyTheme(theme){
|
|
29
|
+
document.documentElement.setAttribute('theme', theme || 'auto');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render(){
|
|
33
|
+
// No UI; control is in Settings accordion. Keep a subtle indicator if needed.
|
|
34
|
+
return html``;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static styles = css`
|
|
38
|
+
:host { display:none; }
|
|
39
|
+
`;
|
|
40
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Simple global settings store with localStorage persistence and event dispatch
|
|
2
|
+
const STORAGE_KEY = 'ktf_settings';
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
showBrowser: false,
|
|
5
|
+
logLevel: 3,
|
|
6
|
+
theme: 'auto',
|
|
7
|
+
delayMs: 0,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let state = (() => {
|
|
11
|
+
try {
|
|
12
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
13
|
+
if (!raw) return { ...DEFAULTS };
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
return { ...DEFAULTS, ...parsed };
|
|
16
|
+
} catch {
|
|
17
|
+
return { ...DEFAULTS };
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
|
|
21
|
+
const save = () => {
|
|
22
|
+
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch {}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const getSettings = () => ({ ...state });
|
|
26
|
+
|
|
27
|
+
export const setSettings = (partial) => {
|
|
28
|
+
state = { ...state, ...partial };
|
|
29
|
+
save();
|
|
30
|
+
const evt = new CustomEvent('ktf-settings-change', { detail: { ...state } });
|
|
31
|
+
window.dispatchEvent(evt);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const subscribe = (handler) => {
|
|
35
|
+
const wrapped = (e) => handler(e.detail);
|
|
36
|
+
window.addEventListener('ktf-settings-change', wrapped);
|
|
37
|
+
return () => window.removeEventListener('ktf-settings-change', wrapped);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Expose on window for debugging if needed
|
|
41
|
+
if (!window.KTF_SETTINGS) {
|
|
42
|
+
window.KTF_SETTINGS = {
|
|
43
|
+
get: getSettings,
|
|
44
|
+
set: setSettings,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="m336-280 144-144 144 144 56-56-144-144 144-144-56-56-144 144-144-144-56 56 144 144-144 144 56 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg eight="24px" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m 840,-240 v -80 H 320 v 80 z m -664,-40 200,-200 -200,-200 -56,56 144,144 -144,144 z m 664,-160 v -80 H 440 v 80 z m 0,-200 v -80 H 320 v 80 z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="m380-300 280-180-280-180v360ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-155.5t86-127Q252-817 325-848.5T480-880q17 0 28.5 11.5T520-840q0 17-11.5 28.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160q133 0 226.5-93.5T800-480q0-17 11.5-28.5T840-520q17 0 28.5 11.5T880-480q0 82-31.5 155t-86 127.5q-54.5 54.5-127 86T480-80Z"></path></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm112-260q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M 440 -920 L 440 -760 L 520 -760 L 520 -920 L 440 -920 z M 818.67969 -885.5332 L 583.66602 -651.38281 C 553.04363 -670.31258 518.55019 -680 480 -680 C 424.66678 -680 377.49992 -660.49992 338.5 -621.5 C 299.50008 -582.50008 280 -535.33322 280 -480 C 280 -441.71314 289.5643 -407.43184 308.23633 -376.96484 L 97.058594 -166.5625 L 168.59375 -92.332031 L 263.80078 -187.18945 C 267.09506 -183.63709 270.46257 -180.11555 273.9375 -176.64062 C 343.93736 -106.64077 428.9377 -71.640625 528.9375 -71.640625 C 628.9373 -71.640625 713.93764 -106.64077 783.9375 -176.64062 C 853.93736 -246.64049 888.9375 -331.64082 888.9375 -431.64062 C 888.9375 -440.97395 888.60417 -450.14064 887.9375 -459.14062 C 887.27083 -468.14062 886.27083 -476.97398 884.9375 -485.64062 C 865.60421 -458.30735 840.60411 -436.47393 809.9375 -420.14062 C 779.27089 -403.80733 745.60409 -395.64062 708.9375 -395.64062 C 648.93762 -395.64062 597.93742 -416.64072 555.9375 -458.64062 C 552.62172 -461.95641 549.47084 -465.3433 546.42188 -468.77344 L 890.21484 -811.30273 L 818.67969 -885.5332 z M 212 -806 L 155 -747 L 256 -650 L 308 -706 L 212 -806 z M 480 -600 C 495.83397 -600 510.50253 -597.28065 524.08008 -592.01562 L 367.83594 -436.34375 C 362.67843 -449.80488 360 -464.32853 360 -480 C 360 -513.33327 371.66671 -541.66671 395 -565 C 418.33329 -588.33329 446.66673 -600 480 -600 z M 40 -520 L 40 -440 L 200 -440 L 200 -520 L 40 -520 z M 489.73047 -412.28906 C 492.87534 -408.86757 496.09809 -405.48004 499.4375 -402.14062 C 557.10405 -344.47408 626.93766 -315.64062 708.9375 -315.64062 C 722.27081 -315.64062 735.60419 -316.64064 748.9375 -318.64062 C 762.27081 -320.64062 775.60419 -323.3073 788.9375 -326.64062 C 767.60421 -274.64074 733.60407 -232.4739 686.9375 -200.14062 C 640.27093 -167.80736 587.60405 -151.64062 528.9375 -151.64062 C 451.60432 -151.64062 385.60406 -178.97407 330.9375 -233.64062 C 327.48999 -237.08813 324.17746 -240.59082 320.95312 -244.13086 L 489.73047 -412.28906 z " /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-360q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm0 80q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM200-440H40v-80h160v80Zm720 0H760v-80h160v80ZM440-760v-160h80v160h-80Zm0 720v-160h80v160h-80ZM256-650l-101-97 57-59 96 100-52 56Zm492 496-97-101 53-55 101 97-57 59Zm-98-550 97-101 59 57-100 96-56-52ZM154-212l101-97 55 53-97 101-59-57Zm326-268Z"/></svg>
|
package/gui/index.html
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Kempo Testing Library GUI</title>
|
|
7
|
+
<link rel="stylesheet" href="/essential.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1 class="ta-center">Kempo Testing Framework GUI</h1>
|
|
12
|
+
|
|
13
|
+
<ktf-collapsible id="settings">
|
|
14
|
+
<span slot="title"><ktf-icon name="settings"></ktf-icon> <b>Settings</b></span>
|
|
15
|
+
|
|
16
|
+
<ktf-setting-checkbox name="showBrowser" label="Show Browser"></ktf-setting-checkbox>
|
|
17
|
+
<div id="delaySettingRow">
|
|
18
|
+
<ktf-setting-number name="delayMs" label="Delay (ms) between tests and around browser runs" step="100" min="0" max="600000" suffix="ms"></ktf-setting-number>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<ktf-setting-select name="logLevel" label="Log Level"></ktf-setting-select>
|
|
22
|
+
|
|
23
|
+
<ktf-setting-select name="theme" label="Theme"></ktf-setting-select>
|
|
24
|
+
|
|
25
|
+
</ktf-collapsible>
|
|
26
|
+
|
|
27
|
+
<ktf-test-framework id="framework">
|
|
28
|
+
<ktf-test-summary id="globalSummary"></ktf-test-summary>
|
|
29
|
+
<div id="nodeTestsContainer">
|
|
30
|
+
<h2>Node Tests</h2>
|
|
31
|
+
<div id="nodeTests"></div>
|
|
32
|
+
</div>
|
|
33
|
+
<div id="browserTestsContainer">
|
|
34
|
+
<h2>Browser Tests</h2>
|
|
35
|
+
<div id="browserTests"></div>
|
|
36
|
+
</div>
|
|
37
|
+
</ktf-test-framework>
|
|
38
|
+
</main>
|
|
39
|
+
<script type="module">
|
|
40
|
+
import '/components/TestSuite.js';
|
|
41
|
+
import '/components/Icon.js';
|
|
42
|
+
import '/components/Theme.js';
|
|
43
|
+
import '/components/Collapsible.js';
|
|
44
|
+
import '/components/SettingSelect.js';
|
|
45
|
+
import '/components/SettingCheckbox.js';
|
|
46
|
+
import '/components/SettingNumber.js';
|
|
47
|
+
import '/components/TestSummary.js';
|
|
48
|
+
import '/components/TestFramework.js';
|
|
49
|
+
import { getSettings, setSettings, subscribe } from '/components/settingsStore.js';
|
|
50
|
+
|
|
51
|
+
// Apply theme to document on load and when settings change
|
|
52
|
+
const applyTheme = (s) => {
|
|
53
|
+
const theme = s.theme || 'auto';
|
|
54
|
+
document.documentElement.setAttribute('theme', theme);
|
|
55
|
+
};
|
|
56
|
+
applyTheme(getSettings());
|
|
57
|
+
subscribe(applyTheme);
|
|
58
|
+
|
|
59
|
+
// Toggle delay setting visibility with showBrowser
|
|
60
|
+
const syncDelayVisibility = (s) => {
|
|
61
|
+
const row = document.getElementById('delaySettingRow');
|
|
62
|
+
if (row) row.style.display = s.showBrowser ? '' : 'none';
|
|
63
|
+
};
|
|
64
|
+
syncDelayVisibility(getSettings());
|
|
65
|
+
subscribe(syncDelayVisibility);
|
|
66
|
+
|
|
67
|
+
// Settings UI init (reserved for future)
|
|
68
|
+
const els = { };
|
|
69
|
+
const applyToControls = () => {};
|
|
70
|
+
applyToControls(getSettings());
|
|
71
|
+
subscribe(applyToControls);
|
|
72
|
+
|
|
73
|
+
const testFiles = await(await fetch('/testFiles')).json();
|
|
74
|
+
for (const browserTest of testFiles.browserTests) {
|
|
75
|
+
try {
|
|
76
|
+
const moduleUrl = `/test/${browserTest.file}`;
|
|
77
|
+
const testModule = await import(moduleUrl);
|
|
78
|
+
browserTest.testNames = testModule.default ? Object.keys(testModule.default) : [];
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`Error importing test file ${browserTest.file}:`, error);
|
|
81
|
+
browserTest.testNames = [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if(!testFiles?.browserTests?.length){
|
|
85
|
+
document.getElementById('browserTestsContainer').style.display = 'none';
|
|
86
|
+
} else {
|
|
87
|
+
const browserTestsContainer = document.getElementById('browserTests');
|
|
88
|
+
testFiles.browserTests.forEach(browserTest => {
|
|
89
|
+
const testFileElement = document.createElement('ktf-test-suite');
|
|
90
|
+
testFileElement.file = browserTest.file;
|
|
91
|
+
testFileElement.testNames = browserTest.testNames;
|
|
92
|
+
browserTestsContainer.appendChild(testFileElement);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if(!testFiles?.nodeTests?.length){
|
|
96
|
+
document.getElementById('nodeTestsContainer').style.display = 'none';
|
|
97
|
+
} else {
|
|
98
|
+
const nodeTestsContainer = document.getElementById('nodeTests');
|
|
99
|
+
testFiles.nodeTests.forEach(nodeTest => {
|
|
100
|
+
const testFileElement = document.createElement('ktf-test-suite');
|
|
101
|
+
testFileElement.file = nodeTest.file;
|
|
102
|
+
testFileElement.testNames = nodeTest.testNames || [];
|
|
103
|
+
nodeTestsContainer.appendChild(testFileElement);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
</script>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|