kempo-testing-framework 1.2.4 → 1.3.3
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 -105
- package/.github/workflows/publish-npm.yml +29 -0
- package/CONTRIBUTING.md +107 -107
- package/gui/components/Collapsible.js +54 -54
- package/gui/components/Icon.js +151 -151
- package/gui/components/Logs.js +73 -73
- package/gui/components/SettingCheckbox.js +42 -42
- package/gui/components/SettingNumber.js +77 -77
- package/gui/components/SettingSelect.js +67 -67
- package/gui/components/TestFramework.js +378 -378
- package/gui/components/TestSuite.js +181 -181
- package/gui/components/TestSummary.js +189 -189
- package/gui/components/Theme.js +40 -40
- package/gui/components/settingsStore.js +46 -46
- package/gui/icons/fail.svg +1 -1
- package/gui/icons/pass.svg +1 -1
- package/gui/icons/running.svg +1 -1
- package/gui/icons/settings.svg +1 -1
- package/package.json +6 -1
- package/src/browserTestServer.js +115 -115
- package/src/cli.js +198 -198
- package/src/gui.js +15 -2
- package/src/runBrowserTests.js +87 -87
- package/src/runTestFiles.js +94 -94
- package/src/runTests.js +83 -83
- package/src/utils/logLevels.js +7 -7
- package/test.html +30 -30
- package/tests/Counter.js +33 -33
- package/tests/cli-flags.node-test.js +54 -54
- package/tests/cli-help.node-test.js +61 -61
- package/tests/cli-loglevel.node-test.js +40 -40
- package/tests/collapsible.browser-test.js +49 -49
- package/tests/counter.browser-test.js +140 -140
- package/tests/example.node-test.js +103 -103
- package/tests/fibonacci.js +92 -92
- package/tests/fibonacci.test.js +285 -285
- package/tests/icon.browser-test.js +54 -54
- package/tests/logs.browser-test.js +47 -47
- package/tests/setting-checkbox.browser-test.js +48 -48
- package/tests/setting-number.browser-test.js +54 -54
- package/tests/setting-select.browser-test.js +47 -47
- package/tests/settings-store.browser-test.js +26 -26
- package/tests/src-browserTestServer.node-test.js +47 -47
- package/tests/src-cli.node-test.js +32 -32
- package/tests/src-findTests.node-test.js +41 -41
- package/tests/src-logLevels.node-test.js +29 -29
- package/tests/src-runBrowserTests.node-test.js +41 -41
- package/tests/src-runTestFiles.node-test.js +42 -42
- package/tests/src-runTests.node-test.js +56 -56
- package/tests/test-framework.browser-test.js +65 -65
- package/tests/test-summary.browser-test.js +78 -78
- package/tests/test.browser-test.js +56 -56
- package/tests/testfile.browser-test.js +60 -60
- package/tests/theme.browser-test.js +38 -38
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
|
|
3
|
-
const runWithArgs = (args, timeoutMs = 3000) => new Promise((resolve) => {
|
|
4
|
-
const child = spawn(process.execPath, ['index.js', ...args, '--debug-flags'], {
|
|
5
|
-
cwd: process.cwd(),
|
|
6
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
7
|
-
});
|
|
8
|
-
let out = '';
|
|
9
|
-
let resolved = false;
|
|
10
|
-
|
|
11
|
-
const finish = () => {
|
|
12
|
-
if (resolved) return;
|
|
13
|
-
resolved = true;
|
|
14
|
-
try { child.kill('SIGTERM'); } catch {}
|
|
15
|
-
resolve(out);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
child.stdout.on('data', (chunk) => {
|
|
19
|
-
out += chunk.toString();
|
|
20
|
-
if (out.includes('kempo-test flags:')) {
|
|
21
|
-
// We only need to see the flags echo. Short delay to capture the full line, then stop.
|
|
22
|
-
setTimeout(finish, 50);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
child.stderr.on('data', (chunk) => { out += chunk.toString(); });
|
|
26
|
-
child.on('error', () => finish());
|
|
27
|
-
child.on('exit', () => finish());
|
|
28
|
-
|
|
29
|
-
setTimeout(finish, timeoutMs);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export default {
|
|
33
|
-
'maps -l 3 to logLevel 3': async ({ pass, fail }) => {
|
|
34
|
-
const out = await runWithArgs(['-l', '3']);
|
|
35
|
-
out.includes('logLevel: 3') ? pass('Parsed -l 3 to logLevel 3') : fail(`Output did not include expected logLevel=3:\n${out}`);
|
|
36
|
-
},
|
|
37
|
-
'maps -l debug to logLevel 4': async ({ pass, fail }) => {
|
|
38
|
-
const out = await runWithArgs(['-l', 'debug']);
|
|
39
|
-
out.includes('logLevel: 4') ? pass('Parsed -l debug to logLevel 4') : fail(`Output did not include expected logLevel=4:\n${out}`);
|
|
40
|
-
},
|
|
41
|
-
'maps -l v to logLevel 3': async ({ pass, fail }) => {
|
|
42
|
-
const out = await runWithArgs(['-l', 'v']);
|
|
43
|
-
out.includes('logLevel: 3') ? pass('Parsed -l v to logLevel 3') : fail(`Output did not include expected logLevel=3:\n${out}`);
|
|
44
|
-
},
|
|
45
|
-
'passes --delay value as delay flag': async ({ pass, fail }) => {
|
|
46
|
-
const out = await runWithArgs(['--delay', '2000']);
|
|
47
|
-
(out.includes("delay: '2000'") || out.includes('delay: 2000')) ? pass('Parsed --delay 2000 into flags output') : fail(`Output did not include expected delay:\n${out}`);
|
|
48
|
-
},
|
|
49
|
-
'combines log-level and delay flags': async ({ pass, fail }) => {
|
|
50
|
-
const out = await runWithArgs(['-l', 'minimal', '--delay', '250']);
|
|
51
|
-
const ok = out.includes('logLevel: 1') && (out.includes("delay: '250'") || out.includes('delay: 250'));
|
|
52
|
-
ok ? pass('Parsed and combined -l minimal with --delay 250') : fail(`Output missing expected flags:\n${out}`);
|
|
53
|
-
},
|
|
54
|
-
};
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
const runWithArgs = (args, timeoutMs = 3000) => new Promise((resolve) => {
|
|
4
|
+
const child = spawn(process.execPath, ['index.js', ...args, '--debug-flags'], {
|
|
5
|
+
cwd: process.cwd(),
|
|
6
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
7
|
+
});
|
|
8
|
+
let out = '';
|
|
9
|
+
let resolved = false;
|
|
10
|
+
|
|
11
|
+
const finish = () => {
|
|
12
|
+
if (resolved) return;
|
|
13
|
+
resolved = true;
|
|
14
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
15
|
+
resolve(out);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
child.stdout.on('data', (chunk) => {
|
|
19
|
+
out += chunk.toString();
|
|
20
|
+
if (out.includes('kempo-test flags:')) {
|
|
21
|
+
// We only need to see the flags echo. Short delay to capture the full line, then stop.
|
|
22
|
+
setTimeout(finish, 50);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
child.stderr.on('data', (chunk) => { out += chunk.toString(); });
|
|
26
|
+
child.on('error', () => finish());
|
|
27
|
+
child.on('exit', () => finish());
|
|
28
|
+
|
|
29
|
+
setTimeout(finish, timeoutMs);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
'maps -l 3 to logLevel 3': async ({ pass, fail }) => {
|
|
34
|
+
const out = await runWithArgs(['-l', '3']);
|
|
35
|
+
out.includes('logLevel: 3') ? pass('Parsed -l 3 to logLevel 3') : fail(`Output did not include expected logLevel=3:\n${out}`);
|
|
36
|
+
},
|
|
37
|
+
'maps -l debug to logLevel 4': async ({ pass, fail }) => {
|
|
38
|
+
const out = await runWithArgs(['-l', 'debug']);
|
|
39
|
+
out.includes('logLevel: 4') ? pass('Parsed -l debug to logLevel 4') : fail(`Output did not include expected logLevel=4:\n${out}`);
|
|
40
|
+
},
|
|
41
|
+
'maps -l v to logLevel 3': async ({ pass, fail }) => {
|
|
42
|
+
const out = await runWithArgs(['-l', 'v']);
|
|
43
|
+
out.includes('logLevel: 3') ? pass('Parsed -l v to logLevel 3') : fail(`Output did not include expected logLevel=3:\n${out}`);
|
|
44
|
+
},
|
|
45
|
+
'passes --delay value as delay flag': async ({ pass, fail }) => {
|
|
46
|
+
const out = await runWithArgs(['--delay', '2000']);
|
|
47
|
+
(out.includes("delay: '2000'") || out.includes('delay: 2000')) ? pass('Parsed --delay 2000 into flags output') : fail(`Output did not include expected delay:\n${out}`);
|
|
48
|
+
},
|
|
49
|
+
'combines log-level and delay flags': async ({ pass, fail }) => {
|
|
50
|
+
const out = await runWithArgs(['-l', 'minimal', '--delay', '250']);
|
|
51
|
+
const ok = out.includes('logLevel: 1') && (out.includes("delay: '250'") || out.includes('delay: 250'));
|
|
52
|
+
ok ? pass('Parsed and combined -l minimal with --delay 250') : fail(`Output missing expected flags:\n${out}`);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
|
|
3
|
-
const runWithHelp = (args, timeoutMs = 3000) => new Promise((resolve) => {
|
|
4
|
-
const child = spawn(process.execPath, ['index.js', ...args], {
|
|
5
|
-
cwd: process.cwd(),
|
|
6
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
7
|
-
});
|
|
8
|
-
let out = '';
|
|
9
|
-
let resolved = false;
|
|
10
|
-
|
|
11
|
-
const finish = () => {
|
|
12
|
-
if (resolved) return;
|
|
13
|
-
resolved = true;
|
|
14
|
-
try { child.kill('SIGTERM'); } catch {}
|
|
15
|
-
resolve(out);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
child.stdout.on('data', (chunk) => {
|
|
19
|
-
out += chunk.toString();
|
|
20
|
-
// Help should exit quickly, check for help content
|
|
21
|
-
if (out.includes('Kempo Testing Framework') && out.includes('USAGE:')) {
|
|
22
|
-
setTimeout(finish, 50);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
child.stderr.on('data', (chunk) => { out += chunk.toString(); });
|
|
26
|
-
child.on('error', () => finish());
|
|
27
|
-
child.on('exit', () => finish());
|
|
28
|
-
|
|
29
|
-
setTimeout(finish, timeoutMs);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export default {
|
|
33
|
-
'--help shows help message and exits': async ({ pass, fail }) => {
|
|
34
|
-
const out = await runWithHelp(['--help']);
|
|
35
|
-
const hasHelp = out.includes('Kempo Testing Framework') &&
|
|
36
|
-
out.includes('USAGE:') &&
|
|
37
|
-
out.includes('OPTIONS:') &&
|
|
38
|
-
out.includes('--help');
|
|
39
|
-
const noTestRun = !out.includes('kempo-test flags:') && !out.includes('Test Summary');
|
|
40
|
-
|
|
41
|
-
if (hasHelp && noTestRun) {
|
|
42
|
-
pass('--help shows help message and exits without running tests');
|
|
43
|
-
} else {
|
|
44
|
-
fail(`Help output incorrect:\nHas help: ${hasHelp}\nNo test run: ${noTestRun}\nOutput:\n${out}`);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
'-h shows help message and exits': async ({ pass, fail }) => {
|
|
48
|
-
const out = await runWithHelp(['-h']);
|
|
49
|
-
const hasHelp = out.includes('Kempo Testing Framework') &&
|
|
50
|
-
out.includes('USAGE:') &&
|
|
51
|
-
out.includes('OPTIONS:') &&
|
|
52
|
-
out.includes('--help');
|
|
53
|
-
const noTestRun = !out.includes('kempo-test flags:') && !out.includes('Test Summary');
|
|
54
|
-
|
|
55
|
-
if (hasHelp && noTestRun) {
|
|
56
|
-
pass('-h shows help message and exits without running tests');
|
|
57
|
-
} else {
|
|
58
|
-
fail(`Help output incorrect:\nHas help: ${hasHelp}\nNo test run: ${noTestRun}\nOutput:\n${out}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
const runWithHelp = (args, timeoutMs = 3000) => new Promise((resolve) => {
|
|
4
|
+
const child = spawn(process.execPath, ['index.js', ...args], {
|
|
5
|
+
cwd: process.cwd(),
|
|
6
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
7
|
+
});
|
|
8
|
+
let out = '';
|
|
9
|
+
let resolved = false;
|
|
10
|
+
|
|
11
|
+
const finish = () => {
|
|
12
|
+
if (resolved) return;
|
|
13
|
+
resolved = true;
|
|
14
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
15
|
+
resolve(out);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
child.stdout.on('data', (chunk) => {
|
|
19
|
+
out += chunk.toString();
|
|
20
|
+
// Help should exit quickly, check for help content
|
|
21
|
+
if (out.includes('Kempo Testing Framework') && out.includes('USAGE:')) {
|
|
22
|
+
setTimeout(finish, 50);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
child.stderr.on('data', (chunk) => { out += chunk.toString(); });
|
|
26
|
+
child.on('error', () => finish());
|
|
27
|
+
child.on('exit', () => finish());
|
|
28
|
+
|
|
29
|
+
setTimeout(finish, timeoutMs);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
'--help shows help message and exits': async ({ pass, fail }) => {
|
|
34
|
+
const out = await runWithHelp(['--help']);
|
|
35
|
+
const hasHelp = out.includes('Kempo Testing Framework') &&
|
|
36
|
+
out.includes('USAGE:') &&
|
|
37
|
+
out.includes('OPTIONS:') &&
|
|
38
|
+
out.includes('--help');
|
|
39
|
+
const noTestRun = !out.includes('kempo-test flags:') && !out.includes('Test Summary');
|
|
40
|
+
|
|
41
|
+
if (hasHelp && noTestRun) {
|
|
42
|
+
pass('--help shows help message and exits without running tests');
|
|
43
|
+
} else {
|
|
44
|
+
fail(`Help output incorrect:\nHas help: ${hasHelp}\nNo test run: ${noTestRun}\nOutput:\n${out}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
'-h shows help message and exits': async ({ pass, fail }) => {
|
|
48
|
+
const out = await runWithHelp(['-h']);
|
|
49
|
+
const hasHelp = out.includes('Kempo Testing Framework') &&
|
|
50
|
+
out.includes('USAGE:') &&
|
|
51
|
+
out.includes('OPTIONS:') &&
|
|
52
|
+
out.includes('--help');
|
|
53
|
+
const noTestRun = !out.includes('kempo-test flags:') && !out.includes('Test Summary');
|
|
54
|
+
|
|
55
|
+
if (hasHelp && noTestRun) {
|
|
56
|
+
pass('-h shows help message and exits without running tests');
|
|
57
|
+
} else {
|
|
58
|
+
fail(`Help output incorrect:\nHas help: ${hasHelp}\nNo test run: ${noTestRun}\nOutput:\n${out}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
|
|
3
|
-
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
4
|
-
|
|
5
|
-
const run = (args, timeoutMs = 8000) => new Promise((resolve) => {
|
|
6
|
-
const child = spawn(process.execPath, ['index.js', ...args], {
|
|
7
|
-
cwd: process.cwd(),
|
|
8
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
9
|
-
});
|
|
10
|
-
let out = '';
|
|
11
|
-
let resolved = false;
|
|
12
|
-
const done = () => { if (!resolved) { resolved = true; try { child.kill('SIGTERM'); } catch {} resolve(out); } };
|
|
13
|
-
child.stdout.on('data', c => { out += c.toString(); });
|
|
14
|
-
child.stderr.on('data', c => { out += c.toString(); });
|
|
15
|
-
child.on('exit', done);
|
|
16
|
-
child.on('error', done);
|
|
17
|
-
setTimeout(done, timeoutMs);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export default {
|
|
21
|
-
'debug level prints debug preamble': async ({ pass, fail }) => {
|
|
22
|
-
// Pass suite filter as positional arg so it isn't consumed as a value for -n
|
|
23
|
-
const out = await run(['-l', 'debug', 'example']);
|
|
24
|
-
const txt = stripAnsi(out);
|
|
25
|
-
const ok = txt.includes('[Debug] Flags:') && txt.includes('[Debug] Args:') && txt.includes('[Debug] Log level: 4');
|
|
26
|
-
ok ? pass('CLI debug level printed expected debug preamble') : fail(`CLI output missing debug preamble:\n${out}`);
|
|
27
|
-
},
|
|
28
|
-
'minimal level omits beforeAll sections': async ({ pass, fail }) => {
|
|
29
|
-
const out = await run(['-l', 'minimal', 'example']);
|
|
30
|
-
const txt = stripAnsi(out);
|
|
31
|
-
const ok = !/beforeAll/.test(txt);
|
|
32
|
-
ok ? pass('Minimal log level omitted beforeAll sections') : fail(`Output contained beforeAll unexpectedly:\n${out}`);
|
|
33
|
-
},
|
|
34
|
-
'verbose shows pass lines for passing tests': async ({ pass, fail }) => {
|
|
35
|
-
const out = await run(['-l', 'verbose', 'example']);
|
|
36
|
-
const txt = stripAnsi(out);
|
|
37
|
-
const ok = /PASS\s+should handle basic string operations/.test(txt);
|
|
38
|
-
ok ? pass('Verbose level printed PASS lines for passing tests') : fail(`Verbose output missing PASS lines:\n${out}`);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
4
|
+
|
|
5
|
+
const run = (args, timeoutMs = 8000) => new Promise((resolve) => {
|
|
6
|
+
const child = spawn(process.execPath, ['index.js', ...args], {
|
|
7
|
+
cwd: process.cwd(),
|
|
8
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
9
|
+
});
|
|
10
|
+
let out = '';
|
|
11
|
+
let resolved = false;
|
|
12
|
+
const done = () => { if (!resolved) { resolved = true; try { child.kill('SIGTERM'); } catch {} resolve(out); } };
|
|
13
|
+
child.stdout.on('data', c => { out += c.toString(); });
|
|
14
|
+
child.stderr.on('data', c => { out += c.toString(); });
|
|
15
|
+
child.on('exit', done);
|
|
16
|
+
child.on('error', done);
|
|
17
|
+
setTimeout(done, timeoutMs);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
'debug level prints debug preamble': async ({ pass, fail }) => {
|
|
22
|
+
// Pass suite filter as positional arg so it isn't consumed as a value for -n
|
|
23
|
+
const out = await run(['-l', 'debug', 'example']);
|
|
24
|
+
const txt = stripAnsi(out);
|
|
25
|
+
const ok = txt.includes('[Debug] Flags:') && txt.includes('[Debug] Args:') && txt.includes('[Debug] Log level: 4');
|
|
26
|
+
ok ? pass('CLI debug level printed expected debug preamble') : fail(`CLI output missing debug preamble:\n${out}`);
|
|
27
|
+
},
|
|
28
|
+
'minimal level omits beforeAll sections': async ({ pass, fail }) => {
|
|
29
|
+
const out = await run(['-l', 'minimal', 'example']);
|
|
30
|
+
const txt = stripAnsi(out);
|
|
31
|
+
const ok = !/beforeAll/.test(txt);
|
|
32
|
+
ok ? pass('Minimal log level omitted beforeAll sections') : fail(`Output contained beforeAll unexpectedly:\n${out}`);
|
|
33
|
+
},
|
|
34
|
+
'verbose shows pass lines for passing tests': async ({ pass, fail }) => {
|
|
35
|
+
const out = await run(['-l', 'verbose', 'example']);
|
|
36
|
+
const txt = stripAnsi(out);
|
|
37
|
+
const ok = /PASS\s+should handle basic string operations/.test(txt);
|
|
38
|
+
ok ? pass('Verbose level printed PASS lines for passing tests') : fail(`Verbose output missing PASS lines:\n${out}`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
// Browser tests for Collapsible component
|
|
2
|
-
|
|
3
|
-
const nextTick = () => new Promise(r => setTimeout(r));
|
|
4
|
-
|
|
5
|
-
export const beforeEach = async () => {
|
|
6
|
-
document.body.innerHTML = '';
|
|
7
|
-
try { localStorage.removeItem('ktf_settings'); } catch {}
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export default {
|
|
11
|
-
'Collapsible toggles content visibility': async ({ pass, fail, log }) => {
|
|
12
|
-
try {
|
|
13
|
-
await import('/gui/components/Collapsible.js');
|
|
14
|
-
const el = document.createElement('ktf-collapsible');
|
|
15
|
-
el.appendChild(document.createElement('div')).textContent = 'Content';
|
|
16
|
-
document.body.appendChild(el);
|
|
17
|
-
await nextTick();
|
|
18
|
-
const btn = el.shadowRoot.querySelector('button.title');
|
|
19
|
-
const openedBefore = el.hasAttribute('opened') || el.opened;
|
|
20
|
-
const hasSlotBefore = !!el.shadowRoot.querySelector('div.bt');
|
|
21
|
-
log(`Before toggle — opened:${openedBefore} slot:${hasSlotBefore}`);
|
|
22
|
-
btn.click();
|
|
23
|
-
await nextTick();
|
|
24
|
-
const openedAfter = el.opened === true;
|
|
25
|
-
const hasSlotAfter = !!el.shadowRoot.querySelector('div.bt');
|
|
26
|
-
log(`After toggle — opened:${openedAfter} slot:${hasSlotAfter}`);
|
|
27
|
-
if (!openedBefore && !hasSlotBefore && openedAfter && hasSlotAfter) pass('Collapsible opened and revealed slotted content');
|
|
28
|
-
else fail('Collapsible did not toggle as expected');
|
|
29
|
-
} catch (e) { fail(e.stack || String(e)); }
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
'Collapsible respects opened attribute and can close': async ({ pass, fail, log }) => {
|
|
33
|
-
try {
|
|
34
|
-
await import('/gui/components/Collapsible.js');
|
|
35
|
-
const el = document.createElement('ktf-collapsible');
|
|
36
|
-
el.setAttribute('opened', '');
|
|
37
|
-
el.appendChild(document.createElement('div')).textContent = 'Content';
|
|
38
|
-
document.body.appendChild(el);
|
|
39
|
-
await nextTick();
|
|
40
|
-
const btn = el.shadowRoot.querySelector('button.title');
|
|
41
|
-
const initiallyOpen = el.opened === true;
|
|
42
|
-
log(`Initially open:${initiallyOpen}`);
|
|
43
|
-
btn.click();
|
|
44
|
-
await nextTick();
|
|
45
|
-
const afterClose = el.opened === false;
|
|
46
|
-
if (initiallyOpen && afterClose) pass('Opened attribute respected and component can close'); else fail('opened attribute not respected or cannot close');
|
|
47
|
-
} catch (e) { fail(e.stack || String(e)); }
|
|
48
|
-
}
|
|
49
|
-
};
|
|
1
|
+
// Browser tests for Collapsible component
|
|
2
|
+
|
|
3
|
+
const nextTick = () => new Promise(r => setTimeout(r));
|
|
4
|
+
|
|
5
|
+
export const beforeEach = async () => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
try { localStorage.removeItem('ktf_settings'); } catch {}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
'Collapsible toggles content visibility': async ({ pass, fail, log }) => {
|
|
12
|
+
try {
|
|
13
|
+
await import('/gui/components/Collapsible.js');
|
|
14
|
+
const el = document.createElement('ktf-collapsible');
|
|
15
|
+
el.appendChild(document.createElement('div')).textContent = 'Content';
|
|
16
|
+
document.body.appendChild(el);
|
|
17
|
+
await nextTick();
|
|
18
|
+
const btn = el.shadowRoot.querySelector('button.title');
|
|
19
|
+
const openedBefore = el.hasAttribute('opened') || el.opened;
|
|
20
|
+
const hasSlotBefore = !!el.shadowRoot.querySelector('div.bt');
|
|
21
|
+
log(`Before toggle — opened:${openedBefore} slot:${hasSlotBefore}`);
|
|
22
|
+
btn.click();
|
|
23
|
+
await nextTick();
|
|
24
|
+
const openedAfter = el.opened === true;
|
|
25
|
+
const hasSlotAfter = !!el.shadowRoot.querySelector('div.bt');
|
|
26
|
+
log(`After toggle — opened:${openedAfter} slot:${hasSlotAfter}`);
|
|
27
|
+
if (!openedBefore && !hasSlotBefore && openedAfter && hasSlotAfter) pass('Collapsible opened and revealed slotted content');
|
|
28
|
+
else fail('Collapsible did not toggle as expected');
|
|
29
|
+
} catch (e) { fail(e.stack || String(e)); }
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
'Collapsible respects opened attribute and can close': async ({ pass, fail, log }) => {
|
|
33
|
+
try {
|
|
34
|
+
await import('/gui/components/Collapsible.js');
|
|
35
|
+
const el = document.createElement('ktf-collapsible');
|
|
36
|
+
el.setAttribute('opened', '');
|
|
37
|
+
el.appendChild(document.createElement('div')).textContent = 'Content';
|
|
38
|
+
document.body.appendChild(el);
|
|
39
|
+
await nextTick();
|
|
40
|
+
const btn = el.shadowRoot.querySelector('button.title');
|
|
41
|
+
const initiallyOpen = el.opened === true;
|
|
42
|
+
log(`Initially open:${initiallyOpen}`);
|
|
43
|
+
btn.click();
|
|
44
|
+
await nextTick();
|
|
45
|
+
const afterClose = el.opened === false;
|
|
46
|
+
if (initiallyOpen && afterClose) pass('Opened attribute respected and component can close'); else fail('opened attribute not respected or cannot close');
|
|
47
|
+
} catch (e) { fail(e.stack || String(e)); }
|
|
48
|
+
}
|
|
49
|
+
};
|