Upload via GUI (69 Dateien)
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="de"><head><meta charset="utf-8"><title>DeskHub Mock</title>
|
||||
<link rel="stylesheet" href="../src/styles.css"></head>
|
||||
<body><div id="app"></div><div id="toast" class="toast hidden"></div>
|
||||
<script>
|
||||
// Test-Attrappe für die Electron-Brücke (window.deskhub) – nur für lokale UI-Tests ohne Server
|
||||
const staff = { id: 1, name: 'Max Muster', email: 'max@example.de', role: 'agent', permissions: { view_reports: true, reply_ticket: true, close_ticket: true, post_note: true, assign_ticket: true, create_ticket: true, manage_teams: true, manage_roles: true, manage_business_hours: true } };
|
||||
const prio = [{ id: 1, name: 'Hoch', color: '#ef4444' }, { id: 2, name: 'Normal', color: '#3b82f6' }];
|
||||
const statuses = [{ value: 'open', label: 'Offen', color: '#3b82f6' }, { value: 'answered', label: 'Beantwortet', color: '#22c55e' }, { value: 'closed', label: 'Geschlossen', color: '#64748b' }, { value: 'resolved', label: 'Gelöst', color: '#16a34a' }];
|
||||
const tk = (n, s, st, over) => ({ id: n, number: 'T-' + (1000 + n), subject: s, requesterName: 'Kunde ' + n, requesterEmail: `k${n}@mail.de`, status: st, priority: prio[n % 2], department: { id: 1, name: 'Technik' }, assignedStaff: n % 3 ? { id: 1, name: 'Max Muster' } : null, isOverdue: !!over, dueLabel: over ? 'vor 2 Std (überfällig)' : 'in 5 Std', isSpam: false, createdAt: '2026-10-12 08:00:00', updatedAt: '2026-10-12 09:1' + n + ':00' });
|
||||
const tickets = [tk(1, 'Drucker druckt nicht', 'open', true), tk(2, 'Passwort zurücksetzen', 'answered'), tk(3, 'Rechnung fehlt', 'open'), tk(4, 'VPN Verbindung bricht ab', 'open', true)];
|
||||
const routes = {
|
||||
'me.php': () => ({ staff }), 'metadata.php': () => ({ staff, departments: [{ id: 1, name: 'Technik' }], priorities: prio, agents: [{ id: 1, name: 'Max Muster' }, { id: 2, name: 'Eva Beispiel' }], statuses, tags: [], liveChatEnabled: true }),
|
||||
'activity.php': () => ({ newReplies: 0, newUnassigned: 0, serverTime: '2026-10-12 10:00:00', health: window.__health || [] }),
|
||||
'dashboard.php': () => ({ myOpen: 3, overdue: 2, unassigned: 1, unanswered: 4, resolvedToday: 5, slaPercent: 92.4, recent: tickets, departments: [{ name: 'Technik', open: 8, overdue: 2 }, { name: 'Vertrieb', open: 3, overdue: 0 }] }),
|
||||
'tickets.php': () => ({ items: tickets, page: 1, perPage: 30, total: 4, hasMore: false }),
|
||||
'ticket.php': () => ({ ticket: { ...tickets[0], source: 'web', firstResponseDueAt: null, resolutionDueAt: '2026-10-12 12:00:00', lockedByOtherName: null, threads: [
|
||||
{ id: 1, authorType: 'user', authorName: 'Kunde 1', message: 'Mein Drucker druckt nichts mehr.\nBitte helft.', isInternalNote: false, createdAt: '2026-10-12 08:00:00', attachments: [{ id: 1, name: 'foto.png', size: 20480 }], comments: [] },
|
||||
{ id: 2, authorType: 'staff', authorName: 'Max Muster', message: 'Wir schauen uns das an.', isInternalNote: false, createdAt: '2026-10-12 08:30:00', attachments: [], comments: [] },
|
||||
{ id: 3, authorType: 'staff', authorName: 'Max Muster', message: 'Treiber prüfen', isInternalNote: true, createdAt: '2026-10-12 08:35:00', attachments: [], comments: [] }] } }),
|
||||
'queues.php': () => ({ items: [{ id: 1, name: 'Meine überfälligen', isShared: false, mine: true, ownerName: 'Max Muster', filters: { status: 'open,pending', assigned: 'me', sla: 'overdue' } }, { id: 2, name: 'Team-Überblick', isShared: true, mine: false, ownerName: 'Eva Beispiel', filters: { status: 'open,pending,answered' } }] }),
|
||||
'report_builder.php': () => ({ config: { dimension: 'department', metrics: ['total', 'open', 'resolved', 'sla_resolve'], from: '2026-09-15', to: '2026-10-15', sort: 'label', desc: false, filters: {} },
|
||||
columns: [{ key: 'label', label: 'Abteilung', unit: '' }, { key: 'total', label: 'Tickets gesamt', unit: '' }, { key: 'open', label: 'Offen', unit: '' }, { key: 'resolved', label: 'Gelöst/Geschlossen', unit: '' }, { key: 'sla_resolve', label: 'SLA Lösung', unit: '%' }],
|
||||
rows: [{ label: 'Technik', total: 42, open: 8, resolved: 34, sla_resolve: 91.2 }, { label: 'Vertrieb', total: 17, open: 3, resolved: 14, sla_resolve: 97.5 }], totals: { total: 59, open: 11, resolved: 48, sla_resolve: 94.4 },
|
||||
dimensions: [{ key: 'department', label: 'Abteilung' }, { key: 'month', label: 'Monat' }, { key: 'agent', label: 'Mitarbeiter' }],
|
||||
metrics: [{ key: 'total', label: 'Tickets gesamt', unit: '' }, { key: 'open', label: 'Offen', unit: '' }, { key: 'resolved', label: 'Gelöst/Geschlossen', unit: '' }, { key: 'sla_resolve', label: 'SLA Lösung', unit: '%' }],
|
||||
saved: [{ id: 1, name: 'Monatsbericht Support', isShared: true, mine: true, ownerName: 'Max Muster' }] }),
|
||||
'canned_responses.php': () => ({ items: [{ id: 1, title: 'Gruß', body: 'Hallo,<br>danke für Ihre Nachricht.', departmentId: null }] }),
|
||||
'customer.php': () => ({ customer: { name: 'Kunde 1', email: 'k1@mail.de' }, tickets }),
|
||||
'teams.php': () => ({ staffOptions: [{ id: 1, name: 'Max Muster' }, { id: 2, name: 'Eva Beispiel' }], items: [{ id: 1, name: 'Support', leadId: 1, leadName: 'Max Muster', notes: '', isActive: true, memberCount: 2, memberIds: [1, 2] }] }),
|
||||
'roles.php': () => ({ permissionKeys: ['reply_ticket', 'close_ticket', 'manage_teams'], defaultAgentPermissions: { reply_ticket: true }, items: [{ id: 1, name: 'Teamleitung', permissions: { reply_ticket: true }, staffCount: 2 }] }),
|
||||
'business_hours.php': () => ({ enabled: true, hours: [1, 2, 3, 4, 5, 6, 0].map((w) => ({ weekday: w, isOpen: w > 0 && w < 6, openTime: '09:00', closeTime: '17:00' })), holidays: [{ id: 1, date: '2026-12-25', name: 'Weihnachten' }] }),
|
||||
};
|
||||
window.__calls = [];
|
||||
window.deskhub = {
|
||||
session: async () => ({ serverUrl: 'https://demo.example.de', staff }),
|
||||
settings: async () => ({ theme: window.__theme || 'dark', autostart: false, closeToTray: true, notifications: true, pollSeconds: 60, version: '1.1.0', packaged: false }),
|
||||
setSettings: async (p) => { if (p.theme) window.__theme = p.theme; return window.deskhub.settings(); },
|
||||
login: async () => ({ staff }), logout: async () => true, notify: async () => {}, badge: async () => {},
|
||||
api: async (req) => { window.__calls.push(req.path); const f = routes[req.path]; return f ? { data: f() } : { error: { message: 'Mock: ' + req.path + ' nicht vorhanden' } }; },
|
||||
onSessionExpired() {}, onNavigate() {},
|
||||
};
|
||||
</script>
|
||||
<script src="../src/app.js"></script><script src="../src/admin-extra.js"></script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,86 @@
|
||||
// UI-Tests der Desktop-App: lädt test/mock.html in einem unsichtbaren Electron-Fenster
|
||||
// (mit Mock-API statt Server) und steuert die echte Oberfläche per Skript.
|
||||
// Start: npm test
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
const failures = [];
|
||||
let passed = 0;
|
||||
const consoleErrors = [];
|
||||
|
||||
function check(name, ok, detail) {
|
||||
if (ok) { passed++; console.log(' ok ' + name); }
|
||||
else { failures.push(name); console.log(' FAIL ' + name + (detail ? ' – ' + detail : '')); }
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
const win = new BrowserWindow({ show: false, width: 1280, height: 800, webPreferences: { contextIsolation: false, nodeIntegration: false } });
|
||||
win.webContents.on('console-message', (_e, level, message) => { if (level >= 3) consoleErrors.push(message); });
|
||||
win.webContents.on('render-process-gone', (_e, d) => { failures.push('Renderer abgestürzt: ' + d.reason); });
|
||||
const run = (code) => win.webContents.executeJavaScript(code);
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const waitFor = async (code, ms = 4000) => {
|
||||
const end = Date.now() + ms;
|
||||
while (Date.now() < end) { if (await run(code).catch(() => false)) return true; await wait(50); }
|
||||
return false;
|
||||
};
|
||||
|
||||
try {
|
||||
await win.loadFile(path.join(__dirname, 'mock.html'));
|
||||
// Mock-Routen, die für die Admin-Seiten fehlen, liefern eine leere Liste statt eines Fehlers
|
||||
await run(`(() => { const o = window.deskhub.api; window.deskhub.api = async (r) => { const res = await o(r); return res.error && /^Mock:/.test(res.error.message) ? { data: { items: [], rules: [], holidays: [], hours: [], staffOptions: [], departments: [], permissionKeys: [], fieldOptions: [], operatorOptions: [], actionOptions: [] } } : res; }; })()`);
|
||||
|
||||
console.log('Start & Navigation');
|
||||
check('App startet und zeigt das Hauptgerüst', await waitFor(`!!document.querySelector('.side') && !!document.getElementById('main')`));
|
||||
check('Seitenleiste enthält Hauptpunkte', await run(`['Übersicht','Tickets','Kunden'].every(t => document.querySelector('.side').innerText.includes(t))`));
|
||||
|
||||
await run(`go('dashboard')`);
|
||||
check('Dashboard zeigt Kennzahlen', await waitFor(`document.getElementById('main').innerText.includes('Drucker druckt nicht')`));
|
||||
|
||||
await run(`go('tickets')`);
|
||||
check('Ticketliste zeigt alle Tickets', await waitFor(`['Drucker druckt nicht','Passwort zurücksetzen','Rechnung fehlt','VPN Verbindung bricht ab'].every(t => document.getElementById('main').innerText.includes(t))`));
|
||||
check('Gespeicherte Ansicht erscheint als Chip', await run(`document.getElementById('main').innerText.includes('Meine überfälligen')`));
|
||||
|
||||
await run(`go('ticket', 1)`);
|
||||
check('Ticketdetail zeigt Betreff und Verlauf', await waitFor(`['Drucker druckt nicht', 'Mein Drucker druckt nichts mehr', 'Treiber prüfen'].every(x => document.getElementById('main').innerText.includes(x))`));
|
||||
check('Ticketdetail zeigt Eigenschaften und Aktionen', await run(`['eigenschaften', 'schließen'].every(x => document.getElementById('main').innerText.toLowerCase().includes(x))`));
|
||||
check('Interne Notiz ist gekennzeichnet', await run(`!!document.querySelector('.thread.note')`));
|
||||
|
||||
await run(`go('reports')`);
|
||||
check('Berichte laden Tabelle mit Summen', await waitFor(`['Technik', 'Vertrieb', '59'].every(x => document.getElementById('main').innerText.includes(x))`));
|
||||
|
||||
console.log('Verwaltung');
|
||||
await run(`state.staff.role = 'admin'`);
|
||||
const sections = await run(`adminSections().map(s => s.key)`);
|
||||
check('Verwaltungsbereiche vorhanden', sections.length >= 10, 'gefunden: ' + sections.length);
|
||||
for (const key of sections) {
|
||||
await run(`go('admin', '${key}')`);
|
||||
const ok = await waitFor(`document.getElementById('main').children.length > 0 && !document.querySelector('#main .warnbox')`, 3000);
|
||||
check('Verwaltung „' + key + '“ rendert ohne Fehler', ok, await run(`(document.querySelector('#main .warnbox')||{}).innerText || ''`));
|
||||
}
|
||||
|
||||
console.log('Theme');
|
||||
await run(`window.deskhub.setSettings({ theme: 'light' }).then(() => { state.prefs = { ...state.prefs, theme: 'light' }; applyTheme(); })`);
|
||||
check('Hell-Modus setzt data-theme', await waitFor(`document.documentElement.dataset.theme === 'light'`));
|
||||
await run(`window.deskhub.setSettings({ theme: 'dark' }).then(() => { state.prefs = { ...state.prefs, theme: 'dark' }; applyTheme(); })`);
|
||||
check('Dunkel-Modus setzt data-theme', await waitFor(`document.documentElement.dataset.theme === 'dark'`));
|
||||
|
||||
console.log('Verbindung & Überwachung');
|
||||
await run(`go('dashboard')`);
|
||||
await run(`window.__origApi = window.deskhub.api; window.deskhub.api = async () => ({ error: { code: 'NETWORK', status: 0, message: 'Server nicht erreichbar.' } }); pollActivity()`);
|
||||
check('Fehlende Verbindung zeigt Banner', await waitFor(`document.getElementById('banners').innerText.includes('Keine Verbindung zum Server')`, 6000));
|
||||
check('Banner bietet „Jetzt erneut versuchen“', await run(`document.getElementById('banners').innerText.includes('Jetzt erneut versuchen')`));
|
||||
await run(`window.deskhub.api = window.__origApi; retryNow()`);
|
||||
check('Banner verschwindet nach Wiederherstellung', await waitFor(`!document.getElementById('banners').innerText.includes('Keine Verbindung zum Server')`, 6000));
|
||||
await run(`window.__health = [{ level: 'error', code: 'mailbox_down', message: 'Postfach „support@example.de“ ist nicht erreichbar.' }]; pollActivity()`);
|
||||
check('Serverstörung (Mailabruf) wird als Banner gezeigt', await waitFor(`document.getElementById('banners').innerText.includes('support@example.de')`, 6000));
|
||||
|
||||
check('Keine JavaScript-Fehler in der Konsole', consoleErrors.length === 0, consoleErrors.slice(0, 3).join(' | '));
|
||||
} catch (e) {
|
||||
failures.push('Testlauf abgebrochen: ' + e.message);
|
||||
console.log(' FAIL ' + e.message);
|
||||
}
|
||||
|
||||
console.log(`\n${passed} bestanden, ${failures.length} fehlgeschlagen`);
|
||||
app.exit(failures.length ? 1 : 0);
|
||||
});
|
||||
Reference in New Issue
Block a user