161 lines
5.6 KiB
JavaScript
161 lines
5.6 KiB
JavaScript
/**
|
|
* Test dymny zbudowanego panelu.
|
|
*
|
|
* Ładuje artefakt z dist/ do jsdom, podstawia atrapę WebSocketu i fetcha,
|
|
* a następnie sprawdza dwie rzeczy: czy interfejs renderuje dane z serwera
|
|
* i czy kliknięcia trafiają w odpowiednie endpointy API.
|
|
*
|
|
* Uruchomienie: npm run build && npm run smoke
|
|
*/
|
|
import { readFileSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { JSDOM } from 'jsdom'
|
|
|
|
const DIST = resolve(dirname(fileURLToPath(import.meta.url)), 'dist')
|
|
|
|
const status = {
|
|
playing: true,
|
|
paused: false,
|
|
loop_single: false,
|
|
loop_playlist: true,
|
|
volume: 42,
|
|
current: {
|
|
id: 'x1',
|
|
title: 'Testowy utwór',
|
|
url: 'https://youtu.be/abc',
|
|
duration: 200,
|
|
elapsed: 12.3,
|
|
plays_left: 4,
|
|
},
|
|
queue: [
|
|
{ id: 'x1', title: 'Testowy utwór', url: 'https://youtu.be/abc', plays_left: 4 },
|
|
{ id: 'x2', title: 'Drugi utwór', url: null, plays_left: 2 },
|
|
],
|
|
queue_index: 0,
|
|
history: [
|
|
{
|
|
id: 'h1',
|
|
title: 'Stary utwór',
|
|
url: 'https://youtu.be/old',
|
|
duration: 65,
|
|
is_local: false,
|
|
filepath: null,
|
|
},
|
|
],
|
|
voice_channels: [{ guild_id: '1', guild_name: 'Serwer', channel_id: '10', channel_name: 'Muzyka' }],
|
|
active_voice_channel: {
|
|
guild_id: '1',
|
|
guild_name: 'Serwer',
|
|
channel_id: '10',
|
|
channel_name: 'Muzyka',
|
|
},
|
|
text_channels: [],
|
|
active_text_channel_id: null,
|
|
guilds: [
|
|
{
|
|
guild_id: '1',
|
|
guild_name: 'Serwer',
|
|
text_channels: [{ channel_id: '20', channel_name: 'ogolny' }],
|
|
active_notification_channel_id: '20',
|
|
},
|
|
],
|
|
default_plays: 5,
|
|
saved_queue_available: true,
|
|
}
|
|
|
|
const calls = []
|
|
const html = readFileSync(`${DIST}/index.html`, 'utf8')
|
|
|
|
const dom = new JSDOM(html, {
|
|
url: 'http://localhost:8000/',
|
|
runScripts: 'dangerously',
|
|
pretendToBeVisual: true,
|
|
beforeParse(window) {
|
|
window.WebSocket = class {
|
|
constructor(url) {
|
|
this.url = url
|
|
setTimeout(() => {
|
|
this.onopen?.()
|
|
this.onmessage?.({ data: JSON.stringify(status) })
|
|
}, 0)
|
|
}
|
|
close() {}
|
|
send() {}
|
|
}
|
|
window.fetch = async (path, init) => {
|
|
calls.push(`${init?.method ?? 'GET'} ${path}`)
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () =>
|
|
String(path).includes('/api/uploads')
|
|
? { files: [{ filename: 'sample.mp3', size_mb: 3.2, mtime: 1 }] }
|
|
: { ...status, mode: 'single' },
|
|
}
|
|
}
|
|
},
|
|
})
|
|
|
|
const scriptSrc = html.match(/src="(\/assets\/[^"]+\.js)"/)[1]
|
|
dom.window.eval(readFileSync(`${DIST}${scriptSrc}`, 'utf8'))
|
|
|
|
const settle = () => new Promise((done) => setTimeout(done, 250))
|
|
await settle()
|
|
|
|
const doc = dom.window.document
|
|
const app = doc.getElementById('app')
|
|
const text = () => app.textContent
|
|
const byTitle = (title) => app.querySelector(`[title="${title}"]`)
|
|
|
|
const results = []
|
|
const check = (name, ok) => results.push([name, ok])
|
|
const hasCall = (expected) => calls.some((call) => call === expected || call.startsWith(`${expected}?`))
|
|
|
|
check('aplikacja zamontowana', app.children.length > 0)
|
|
check('nagłówek', text().includes('ŻyrarBOT'))
|
|
check('status połączenia', text().includes('Połączony: Muzyka (Serwer)'))
|
|
check('aktualny utwór', text().includes('Testowy utwór'))
|
|
check('drugi utwór z kolejki', text().includes('Drugi utwór'))
|
|
check('odmiana licznika kolejki', text().includes('2 utwory'))
|
|
check('historia', text().includes('Stary utwór'))
|
|
check('plik z serwerowej bazy', text().includes('sample.mp3'))
|
|
check('odmiana licznika plików', text().includes('1 plik'))
|
|
check('badge zapętlania kolejki', text().includes('Zapętlenie kolejki'))
|
|
check('głośność z serwera', text().includes('42%'))
|
|
check('sformatowany czas trwania', text().includes('3:20'))
|
|
check('przycisk Odłącz przy połączeniu', text().includes('Odłącz'))
|
|
check('ikony renderowane jako SVG', app.querySelectorAll('svg').length > 10)
|
|
check('pozycje list', app.querySelectorAll('.queue-item').length === 4)
|
|
check('podświetlona aktywna pozycja', app.querySelectorAll('.playing-item').length === 1)
|
|
check('animacja winylu przy odtwarzaniu', app.querySelector('.vinyl-record.playing') !== null)
|
|
check('pobrano listę plików na starcie', hasCall('GET /api/uploads'))
|
|
check('przycisk przywracania kolejki', text().includes('Przywróć ostatnią kolejkę'))
|
|
|
|
// --- Interakcje: kliknięcie ma trafić w konkretny endpoint ---------------
|
|
|
|
async function clickExpects(title, expected) {
|
|
const before = calls.length
|
|
byTitle(title)?.click()
|
|
await settle()
|
|
check(`${title} -> ${expected}`, calls.slice(before).some((call) =>
|
|
call === expected || call.startsWith(`${expected}?`)))
|
|
}
|
|
|
|
await clickExpects('Wstrzymaj', 'POST /api/pause')
|
|
await clickExpects('Pomiń utwór', 'POST /api/skip')
|
|
await clickExpects('Zatrzymaj i wyczyść', 'POST /api/stop')
|
|
await clickExpects('Zapętlenie: cała kolejka (kliknij, aby zapętlić jeden utwór)', 'POST /api/loop_cycle')
|
|
await clickExpects('Odtwarzaj od tej pozycji', 'POST /api/jump')
|
|
await clickExpects('Usuń z kolejki', 'POST /api/remove')
|
|
await clickExpects('Przesuń w dół', 'POST /api/reorder')
|
|
await clickExpects('Zwiększ o 1', 'POST /api/queue/plays')
|
|
await clickExpects('Dodaj do kolejki', 'POST /api/play_file')
|
|
await clickExpects('Dodaj ponownie do kolejki', 'POST /api/play')
|
|
await clickExpects('Usuń z serwera', 'DELETE /api/uploads/sample.mp3')
|
|
|
|
const failed = results.filter(([, ok]) => !ok)
|
|
for (const [name, ok] of results) console.log(`${ok ? 'OK ' : 'FAIL'} ${name}`)
|
|
console.log(failed.length ? `\n${failed.length} z ${results.length} niepowodzeń` : `\nOK - ${results.length} sprawdzeń`)
|
|
process.exit(failed.length ? 1 : 0)
|