plum-e2e 1.0.10 → 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/.claude/settings.local.json +16 -1
- package/.vscode/settings.json +10 -0
- package/README.md +151 -37
- package/backend/_scaffold/features/LoginPage.feature +45 -3
- package/backend/_scaffold/pages/HomepagePage.ts +7 -0
- package/backend/_scaffold/pages/LoginPage.ts +37 -13
- package/backend/_scaffold/step_definitions/HomepageSteps.ts +6 -0
- package/backend/_scaffold/step_definitions/LoginSteps.ts +30 -4
- package/backend/_scaffold/utils/browser.ts +33 -0
- package/backend/_scaffold/utils/hooks.ts +8 -29
- package/backend/_scaffold/utils/utils.ts +3 -9
- package/backend/config/scripts/create-settings.js +7 -14
- package/backend/config/scripts/create-step.mjs +268 -0
- package/backend/config/scripts/generate-report.js +31 -75
- package/backend/config/scripts/run-tests.js +19 -4
- package/backend/package-lock.json +56 -641
- package/backend/package.json +4 -1
- package/backend/routes/reports.routes.js +6 -10
- package/backend/services/envService.js +4 -10
- package/backend/services/reportService.js +70 -20
- package/backend/services/testService.js +99 -24
- package/backend/tsconfig.json +2 -2
- package/backend/websockets/socketHandler.js +12 -6
- package/bin/plum.js +49 -3
- package/frontend/package-lock.json +436 -135
- package/frontend/package.json +1 -1
- package/frontend/src/app.css +241 -6
- package/frontend/src/app.html +14 -1
- package/frontend/src/lib/api/reports.js +68 -0
- package/frontend/src/lib/api/schedules.js +64 -0
- package/frontend/src/lib/api/tests.js +41 -0
- package/frontend/src/lib/components/layout/Nav.svelte +304 -0
- package/frontend/src/lib/components/layout/PageShell.svelte +28 -0
- package/frontend/src/lib/components/layout/RunnerPanel.svelte +378 -0
- package/frontend/src/lib/components/ui/Badge.svelte +63 -0
- package/frontend/src/lib/components/ui/Button.svelte +117 -0
- package/frontend/src/lib/components/ui/Modal.svelte +140 -0
- package/frontend/src/lib/components/ui/Pagination.svelte +100 -0
- package/frontend/src/lib/components/ui/Terminal.svelte +100 -0
- package/frontend/src/lib/stores/runner.js +55 -0
- package/frontend/src/lib/stores/theme.js +47 -0
- package/frontend/src/routes/+layout.svelte +7 -12
- package/frontend/src/routes/+page.svelte +690 -142
- package/frontend/src/routes/reports/+page.svelte +395 -125
- package/frontend/src/routes/reports/[slug]/+page.svelte +749 -0
- package/frontend/src/routes/scheduled-tests/+page.svelte +267 -303
- package/frontend/svelte.config.js +1 -4
- package/frontend/tailwind.config.js +2 -23
- package/package.json +2 -2
- package/backend/_scaffold/utils/world.ts +0 -25
- package/frontend/src/routes/components/Navigation.svelte +0 -53
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
/** @type {'pass' | 'fail' | 'tag' | 'schedule' | 'neutral'} */
|
|
20
|
+
export let variant = 'neutral';
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<span class="badge {variant}"><slot /></span>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
.badge {
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
font-size: 0.68rem;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
letter-spacing: 0.05em;
|
|
32
|
+
text-transform: uppercase;
|
|
33
|
+
padding: 0.2rem 0.6rem;
|
|
34
|
+
border-radius: 100px;
|
|
35
|
+
white-space: nowrap;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.pass {
|
|
39
|
+
background: var(--pass-soft);
|
|
40
|
+
color: var(--pass);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.fail {
|
|
44
|
+
background: var(--fail-soft);
|
|
45
|
+
color: var(--fail);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.tag {
|
|
49
|
+
background: var(--accent-soft);
|
|
50
|
+
color: var(--accent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.schedule {
|
|
54
|
+
background: var(--warn-soft);
|
|
55
|
+
color: var(--warn);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.neutral {
|
|
59
|
+
background: var(--bg-subtle);
|
|
60
|
+
color: var(--text-muted);
|
|
61
|
+
border: 1px solid var(--border);
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
/** @type {'primary' | 'ghost' | 'danger' | 'outline'} */
|
|
20
|
+
export let variant = 'primary';
|
|
21
|
+
/** @type {'sm' | 'md'} */
|
|
22
|
+
export let size = 'md';
|
|
23
|
+
export let type = 'button';
|
|
24
|
+
export let disabled = false;
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<button {type} {disabled} class="btn {variant} {size}" on:click>
|
|
28
|
+
<slot />
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.btn {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
gap: 0.375rem;
|
|
37
|
+
font-family: var(--font-body);
|
|
38
|
+
font-weight: 400;
|
|
39
|
+
border-radius: var(--radius-md);
|
|
40
|
+
border: 1px solid transparent;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
transition:
|
|
45
|
+
background var(--duration-fast) var(--ease-out),
|
|
46
|
+
color var(--duration-fast) var(--ease-out),
|
|
47
|
+
border-color var(--duration-fast) var(--ease-out),
|
|
48
|
+
box-shadow var(--duration-fast) var(--ease-out),
|
|
49
|
+
transform var(--duration-fast) var(--ease-out),
|
|
50
|
+
opacity var(--duration-fast);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.btn:active:not(:disabled) {
|
|
54
|
+
transform: scale(0.97);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.btn:disabled {
|
|
58
|
+
opacity: 0.38;
|
|
59
|
+
cursor: not-allowed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Sizes */
|
|
63
|
+
.sm {
|
|
64
|
+
font-size: 0.75rem;
|
|
65
|
+
padding: 0.3rem 0.75rem;
|
|
66
|
+
border-radius: var(--radius-sm);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.md {
|
|
70
|
+
font-size: 0.875rem;
|
|
71
|
+
padding: 0.55rem 1.25rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Variants */
|
|
75
|
+
.primary {
|
|
76
|
+
background: var(--accent);
|
|
77
|
+
color: #fff;
|
|
78
|
+
border-color: var(--accent);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.primary:hover:not(:disabled) {
|
|
82
|
+
filter: brightness(1.12);
|
|
83
|
+
box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 30%, transparent);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ghost {
|
|
87
|
+
background: transparent;
|
|
88
|
+
color: var(--text-muted);
|
|
89
|
+
border-color: var(--border);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ghost:hover:not(:disabled) {
|
|
93
|
+
background: var(--bg-subtle);
|
|
94
|
+
color: var(--text);
|
|
95
|
+
border-color: var(--text-muted);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.danger {
|
|
99
|
+
background: transparent;
|
|
100
|
+
color: var(--fail);
|
|
101
|
+
border-color: var(--fail);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.danger:hover:not(:disabled) {
|
|
105
|
+
background: var(--fail-soft);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.outline {
|
|
109
|
+
background: transparent;
|
|
110
|
+
color: var(--accent);
|
|
111
|
+
border-color: var(--accent);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.outline:hover:not(:disabled) {
|
|
115
|
+
background: var(--accent-soft);
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { fade, scale } from 'svelte/transition';
|
|
20
|
+
import { cubicOut } from 'svelte/easing';
|
|
21
|
+
|
|
22
|
+
export let open = false;
|
|
23
|
+
export let title = '';
|
|
24
|
+
|
|
25
|
+
function close() {
|
|
26
|
+
open = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function onBackdrop(e) {
|
|
30
|
+
if (e.target === e.currentTarget) close();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function onKeydown(e) {
|
|
34
|
+
if (e.key === 'Escape') close();
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<svelte:window on:keydown={onKeydown} />
|
|
39
|
+
|
|
40
|
+
{#if open}
|
|
41
|
+
<div
|
|
42
|
+
class="backdrop"
|
|
43
|
+
role="presentation"
|
|
44
|
+
on:click={onBackdrop}
|
|
45
|
+
on:keydown={(e) => e.key === 'Escape' && close()}
|
|
46
|
+
transition:fade={{ duration: 200 }}
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
class="panel"
|
|
50
|
+
role="dialog"
|
|
51
|
+
aria-modal="true"
|
|
52
|
+
aria-label={title}
|
|
53
|
+
transition:scale={{ start: 0.96, duration: 260, easing: cubicOut }}
|
|
54
|
+
>
|
|
55
|
+
<div class="header">
|
|
56
|
+
<h3 class="title">{title}</h3>
|
|
57
|
+
<button class="close-btn" on:click={close} aria-label="Close">
|
|
58
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
59
|
+
<path
|
|
60
|
+
d="M1 1l12 12M13 1L1 13"
|
|
61
|
+
stroke="currentColor"
|
|
62
|
+
stroke-width="1.5"
|
|
63
|
+
stroke-linecap="round"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="body">
|
|
69
|
+
<slot />
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
.backdrop {
|
|
77
|
+
position: fixed;
|
|
78
|
+
inset: 0;
|
|
79
|
+
background: rgba(0, 0, 0, 0.45);
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
z-index: 50;
|
|
84
|
+
padding: 1rem;
|
|
85
|
+
backdrop-filter: blur(2px);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.panel {
|
|
89
|
+
background: var(--bg-elevated);
|
|
90
|
+
border: 1px solid var(--border);
|
|
91
|
+
border-radius: var(--radius-lg);
|
|
92
|
+
padding: 1.75rem;
|
|
93
|
+
width: 100%;
|
|
94
|
+
max-width: 480px;
|
|
95
|
+
box-shadow:
|
|
96
|
+
0 4px 6px rgba(0, 0, 0, 0.04),
|
|
97
|
+
0 24px 64px rgba(0, 0, 0, 0.14);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.header {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: space-between;
|
|
104
|
+
margin-bottom: 1.5rem;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.title {
|
|
108
|
+
font-family: var(--font-display);
|
|
109
|
+
font-size: 1.2rem;
|
|
110
|
+
font-weight: 400;
|
|
111
|
+
color: var(--text);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.close-btn {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
width: 28px;
|
|
119
|
+
height: 28px;
|
|
120
|
+
border-radius: var(--radius-sm);
|
|
121
|
+
border: none;
|
|
122
|
+
background: transparent;
|
|
123
|
+
color: var(--text-muted);
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
transition:
|
|
126
|
+
background var(--duration-fast),
|
|
127
|
+
color var(--duration-fast);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.close-btn:hover {
|
|
131
|
+
background: var(--bg-subtle);
|
|
132
|
+
color: var(--text);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.body {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 1rem;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { createEventDispatcher } from 'svelte';
|
|
20
|
+
|
|
21
|
+
export let current = 1;
|
|
22
|
+
export let total = 1;
|
|
23
|
+
|
|
24
|
+
const dispatch = createEventDispatcher();
|
|
25
|
+
|
|
26
|
+
function go(page) {
|
|
27
|
+
if (page < 1 || page > total) return;
|
|
28
|
+
dispatch('change', page);
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#if total > 1}
|
|
33
|
+
<div class="pagination">
|
|
34
|
+
<button
|
|
35
|
+
class="arrow"
|
|
36
|
+
on:click={() => go(current - 1)}
|
|
37
|
+
disabled={current === 1}
|
|
38
|
+
aria-label="Previous"
|
|
39
|
+
>
|
|
40
|
+
←
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
{#each Array(total) as _, i}
|
|
44
|
+
<button class="page" class:active={current === i + 1} on:click={() => go(i + 1)}>
|
|
45
|
+
{i + 1}
|
|
46
|
+
</button>
|
|
47
|
+
{/each}
|
|
48
|
+
|
|
49
|
+
<button
|
|
50
|
+
class="arrow"
|
|
51
|
+
on:click={() => go(current + 1)}
|
|
52
|
+
disabled={current === total}
|
|
53
|
+
aria-label="Next"
|
|
54
|
+
>
|
|
55
|
+
→
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
{/if}
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.pagination {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
gap: 0.25rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
button {
|
|
69
|
+
min-width: 2rem;
|
|
70
|
+
height: 2rem;
|
|
71
|
+
padding: 0 0.25rem;
|
|
72
|
+
border-radius: var(--radius-sm);
|
|
73
|
+
border: 1px solid var(--border);
|
|
74
|
+
background: transparent;
|
|
75
|
+
color: var(--text-muted);
|
|
76
|
+
font-family: var(--font-body);
|
|
77
|
+
font-size: 0.8125rem;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
transition:
|
|
80
|
+
background var(--duration-fast),
|
|
81
|
+
color var(--duration-fast),
|
|
82
|
+
border-color var(--duration-fast);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
button:disabled {
|
|
86
|
+
opacity: 0.3;
|
|
87
|
+
cursor: not-allowed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
button.active {
|
|
91
|
+
background: var(--accent);
|
|
92
|
+
color: #fff;
|
|
93
|
+
border-color: var(--accent);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
button:hover:not(:disabled):not(.active) {
|
|
97
|
+
background: var(--bg-subtle);
|
|
98
|
+
color: var(--text);
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { afterUpdate } from 'svelte';
|
|
20
|
+
|
|
21
|
+
export let output = '';
|
|
22
|
+
|
|
23
|
+
let el;
|
|
24
|
+
|
|
25
|
+
afterUpdate(() => {
|
|
26
|
+
if (el) el.scrollTop = el.scrollHeight;
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class="terminal-wrap">
|
|
31
|
+
<div class="terminal-bar">
|
|
32
|
+
<span class="dot red"></span>
|
|
33
|
+
<span class="dot yellow"></span>
|
|
34
|
+
<span class="dot green"></span>
|
|
35
|
+
</div>
|
|
36
|
+
<pre bind:this={el} class="terminal">{output}</pre>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
.terminal-wrap {
|
|
41
|
+
border-radius: var(--radius-md);
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.terminal-bar {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 6px;
|
|
50
|
+
padding: 0.6rem 0.875rem;
|
|
51
|
+
background: rgba(0, 0, 0, 0.3);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.dot {
|
|
55
|
+
display: block;
|
|
56
|
+
width: 10px;
|
|
57
|
+
height: 10px;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.red {
|
|
62
|
+
background: #ff5f57;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.yellow {
|
|
66
|
+
background: #febc2e;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.green {
|
|
70
|
+
background: #28c840;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.terminal {
|
|
74
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
|
|
75
|
+
font-size: 0.78rem;
|
|
76
|
+
line-height: 1.75;
|
|
77
|
+
background: var(--terminal-bg);
|
|
78
|
+
color: var(--terminal-text);
|
|
79
|
+
padding: 1rem;
|
|
80
|
+
min-height: 220px;
|
|
81
|
+
max-height: 280px;
|
|
82
|
+
overflow-y: auto;
|
|
83
|
+
white-space: pre-wrap;
|
|
84
|
+
word-break: break-word;
|
|
85
|
+
margin: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.terminal::-webkit-scrollbar {
|
|
89
|
+
width: 4px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.terminal::-webkit-scrollbar-track {
|
|
93
|
+
background: transparent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.terminal::-webkit-scrollbar-thumb {
|
|
97
|
+
background: rgba(255, 255, 255, 0.1);
|
|
98
|
+
border-radius: 2px;
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { writable, get } from 'svelte/store';
|
|
19
|
+
|
|
20
|
+
export const socket = writable(null);
|
|
21
|
+
|
|
22
|
+
export const runnerState = writable({
|
|
23
|
+
output: 'Ready — select a test from the list.\n',
|
|
24
|
+
running: false,
|
|
25
|
+
testCompleted: false,
|
|
26
|
+
latestReport: null,
|
|
27
|
+
status: 'idle', // 'idle' | 'running' | 'pass' | 'fail'
|
|
28
|
+
lastRunId: ''
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const runnerConfig = writable({
|
|
32
|
+
workers: 1,
|
|
33
|
+
testID: ''
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const panelExpanded = writable(true);
|
|
37
|
+
|
|
38
|
+
export function triggerRun(id) {
|
|
39
|
+
const s = get(socket);
|
|
40
|
+
if (!s) return;
|
|
41
|
+
|
|
42
|
+
const { workers, testID } = get(runnerConfig);
|
|
43
|
+
const runId = (id !== undefined ? id : testID).trim().replace(/\sOR\s/gi, (m) => m.toLowerCase());
|
|
44
|
+
|
|
45
|
+
runnerState.set({
|
|
46
|
+
output: `Running: ${runId || '(all tests)'}\n`,
|
|
47
|
+
running: true,
|
|
48
|
+
testCompleted: false,
|
|
49
|
+
latestReport: null,
|
|
50
|
+
status: 'running',
|
|
51
|
+
lastRunId: runId
|
|
52
|
+
});
|
|
53
|
+
panelExpanded.set(true);
|
|
54
|
+
s.emit('run-test', runId, workers);
|
|
55
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
This file is part of Plum.
|
|
20
|
+
|
|
21
|
+
Plum is free software: you can redistribute it and/or modify
|
|
22
|
+
it under the terms of the GNU General Public License as published by
|
|
23
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
24
|
+
(at your option) any later version.
|
|
25
|
+
|
|
26
|
+
Plum is distributed in the hope that it will be useful,
|
|
27
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
28
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
29
|
+
GNU General Public License for more details.
|
|
30
|
+
|
|
31
|
+
You should have received a copy of the GNU General Public License
|
|
32
|
+
along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { writable } from 'svelte/store';
|
|
36
|
+
import { browser } from '$app/environment';
|
|
37
|
+
|
|
38
|
+
const KEY = 'plum-theme';
|
|
39
|
+
const initial = browser ? (localStorage.getItem(KEY) ?? 'light') : 'light';
|
|
40
|
+
|
|
41
|
+
export const theme = writable(initial);
|
|
42
|
+
|
|
43
|
+
theme.subscribe((val) => {
|
|
44
|
+
if (!browser) return;
|
|
45
|
+
localStorage.setItem(KEY, val);
|
|
46
|
+
document.documentElement.setAttribute('data-theme', val);
|
|
47
|
+
});
|
|
@@ -17,18 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
<script>
|
|
19
19
|
import '../app.css';
|
|
20
|
-
import
|
|
20
|
+
import Nav from '$lib/components/layout/Nav.svelte';
|
|
21
|
+
import PageShell from '$lib/components/layout/PageShell.svelte';
|
|
22
|
+
import RunnerPanel from '$lib/components/layout/RunnerPanel.svelte';
|
|
21
23
|
</script>
|
|
22
24
|
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
+
<Nav />
|
|
26
|
+
<PageShell>
|
|
25
27
|
<slot />
|
|
26
|
-
</
|
|
27
|
-
|
|
28
|
-
<style>
|
|
29
|
-
.layout {
|
|
30
|
-
display: flex;
|
|
31
|
-
flex-direction: column;
|
|
32
|
-
min-height: 100vh; /* Full viewport height */
|
|
33
|
-
}
|
|
34
|
-
</style>
|
|
28
|
+
</PageShell>
|
|
29
|
+
<RunnerPanel />
|