This commit is contained in:
2026-10-18 05:25:23 +07:00
parent f5e645ec00
commit 30999630f0
18 changed files with 1023 additions and 185 deletions
+204 -4
View File
@@ -1,6 +1,8 @@
const assert = require('node:assert/strict')
const { createServer } = require('node:http')
const { readFileSync } = require('node:fs')
const { readFileSync, writeFileSync, mkdtempSync, rmSync } = require('node:fs')
const { tmpdir } = require('node:os')
const { dialog } = require('electron')
const { join } = require('node:path')
module.exports = async function onlineSmoke({
@@ -31,6 +33,17 @@ module.exports = async function onlineSmoke({
let timerLabel = 'Перерыв'
let timerDurationSeconds = 600
let timerState = 1
const library = [
{
ID: 9,
name: 'Редактор музыки',
tracks: [
{ ID: 91, name: 'Раз', filename: 'music-a.wav', position: 0 },
{ ID: 92, name: 'Два', filename: 'music-b.wav', position: 1 },
],
},
]
let nextPlaylistId = 10
const requests = []
const server = createServer(async (req, res) => {
const chunks = []
@@ -102,8 +115,58 @@ module.exports = async function onlineSmoke({
version++
return send({})
}
if (path === '/api/games/6/music')
return send(music ? { state: music, gamePlaylistId: 1, playlists: [music.playlist] } : {})
if (path === '/api/playlists') {
if (req.method === 'POST') library.push({ ID: nextPlaylistId++, name: body.name, tracks: [] })
return send({ playlists: library })
}
const playlistMatch = path.match(/^\/api\/playlists\/(\d+)(?:\/tracks(?:\/(\d+|order))?)?$/)
if (playlistMatch) {
const item = library.find((p) => p.ID === Number(playlistMatch[1]))
if (!item) return send({}, 404)
if (path.endsWith('/order')) {
item.tracks = body.trackIDs.map((id, position) => ({
...item.tracks.find((t) => t.ID === id),
position,
}))
if (music?.playlist.ID === item.ID) {
const trackID = music.playlist.tracks[music.trackIndex].ID
music = {
...music,
stateVersion: String(Number(music.stateVersion) + 1),
playlist: structuredClone(item),
trackIndex: item.tracks.findIndex((t) => t.ID === trackID),
}
}
} else if (path.endsWith('/tracks') && req.method === 'POST') {
assert.equal(body.filename, 'native.mp3')
assert.equal(Buffer.from(body.data, 'base64').toString(), 'ID3test')
item.tracks.push({
ID: 93,
name: body.name,
filename: 'native.mp3',
position: item.tracks.length,
})
} else if (playlistMatch[2] && req.method === 'DELETE')
item.tracks = item.tracks.filter((t) => t.ID !== Number(playlistMatch[2]))
else if (req.method === 'PUT') item.name = body.name
else if (req.method === 'DELETE') library.splice(library.indexOf(item), 1)
return send({ playlist: item })
}
if (path === '/api/games/6/music') {
if (req.method === 'POST') {
const item = library.find((p) => p.ID === body.playlistID)
music = {
...body,
stateVersion: String(Number(music?.stateVersion || 0) + 1),
playlist: structuredClone(item),
}
}
return send(
music
? { state: music, gamePlaylistId: music.playlist.ID, playlists: [music.playlist] }
: {},
)
}
if (
['/api/files/intro.wav', '/api/files/music-a.wav', '/api/files/music-b.wav'].includes(path)
) {
@@ -118,7 +181,10 @@ module.exports = async function onlineSmoke({
return res.end(readFileSync(join(root, 'fixtures/audio.wav')))
}
if (path === '/api/screens') return send({ screenID: '67', accessToken: 'TEST-SCREEN' })
if (path === '/api/screens/67/bind') return send({})
if (path === '/api/screens/67/bind') {
assert.equal(body.initializeAudio, true)
return send({})
}
if (path === '/api/screens/state') {
polls++
assert.equal(req.headers.authorization, undefined)
@@ -188,6 +254,19 @@ module.exports = async function onlineSmoke({
),
'server game selector',
)
await poll(
() =>
evaluate(
`document.querySelectorAll('main.control > .n-card').length === 4 && document.querySelectorAll('.n-card-header__main').length === 5`,
),
'five control cards',
)
assert.deepEqual(
await evaluate(
`Array.from(document.querySelectorAll('.n-card-header__main')).map(e => e.textContent.trim())`,
),
['Сервер', 'Игра', 'Экран', 'План экранов', 'План музыки'],
)
varyGames = true
for (let refresh = 0; refresh < 5; refresh++) {
await poll(
@@ -541,6 +620,127 @@ module.exports = async function onlineSmoke({
await clickPlan('Сохранить план')
await poll(async () => (await state()).catalog.steps.length === 0, 'plan deleted')
console.log('SCREEN_PLAN_SMOKE_PASS')
const clickMusic = async (label) =>
poll(
() =>
evaluate(`(() => {
const button = Array.from(document.querySelectorAll('[data-testid="music-plan-editor"] button')).find(b => b.textContent.trim() === ${JSON.stringify(label)})
if (!button || button.disabled) return false
button.click(); return true
})()`),
`music button ${label}`,
)
const choosePlaylist = async (name) => {
await poll(
() =>
evaluate(
`(() => { const select = document.querySelector('[data-testid="music-plan-editor"] .n-base-selection'); return select && !select.classList.contains('n-base-selection--disabled') })()`,
),
'library selector ready',
)
await evaluate(
`document.querySelector('[data-testid="music-plan-editor"] .n-base-selection').click()`,
)
await poll(
() =>
evaluate(
`Array.from(document.querySelectorAll('.n-base-select-option')).some(e => e.textContent.trim() === ${JSON.stringify(name)})`,
),
'library option',
)
await evaluate(
`Array.from(document.querySelectorAll('.n-base-select-option')).find(e => e.textContent.trim() === ${JSON.stringify(name)}).click()`,
)
}
const confirmDelete = async (title) => {
await poll(
() =>
evaluate(`(() => {
const dialog = Array.from(document.querySelectorAll('.n-dialog')).find(d => d.textContent.includes(${JSON.stringify(title)}))
const button = dialog && Array.from(dialog.querySelectorAll('button')).find(b => b.textContent.trim() === 'Удалить')
if (!button || button.disabled) return false
button.click(); return true
})()`),
'delete confirmation ' + title,
)
}
await clickMusic('Обновить плейлисты')
await setInput('#new-playlist-name', 'Создан из приложения')
await clickMusic('Создать плейлист')
await poll(() => library.some((p) => p.name === 'Создан из приложения'), 'playlist created')
await poll(
() => evaluate(`document.querySelector('#playlist-name')?.value === 'Создан из приложения'`),
'new playlist selected',
)
await setInput('#playlist-name', 'Переименован')
// Повторный snapshot не затирает ввод названия.
await new Promise((resolve) => setTimeout(resolve, 650))
assert.equal(await evaluate(`document.querySelector('#playlist-name').value`), 'Переименован')
await clickMusic('Сохранить название')
await poll(() => library.some((p) => p.name === 'Переименован'), 'playlist renamed')
const uploadDirectory = mkdtempSync(join(tmpdir(), 'screen-mp3-smoke-'))
const originalDialog = dialog.showOpenDialog
try {
const file = join(uploadDirectory, 'native.mp3')
writeFileSync(file, 'ID3test')
dialog.showOpenDialog = async () => ({ canceled: false, filePaths: [file] })
await clickMusic('Добавить MP3')
await poll(
() =>
evaluate(
`document.querySelector('[data-testid="music-plan-editor"] ol')?.textContent.includes('native.mp3')`,
),
'native MP3 uploaded',
)
} finally {
dialog.showOpenDialog = originalDialog
rmSync(uploadDirectory, { recursive: true, force: true })
}
await clickMusic('Удалить трек')
await confirmDelete('Удалить трек?')
await poll(
() =>
evaluate(
`!document.querySelector('[data-testid="music-plan-editor"] ol')?.textContent.includes('native.mp3')`,
),
'track removed',
)
await clickMusic('Удалить плейлист')
await confirmDelete('Удалить плейлист?')
await poll(() => !library.some((p) => p.name === 'Переименован'), 'playlist removed')
await choosePlaylist('Редактор музыки')
await clickMusic('Использовать в игре')
await poll(
async () => (await state()).channels.music.playlistId === 9,
'playlist assigned to game',
)
music = { ...music, playback: 2, stateVersion: String(Number(music.stateVersion) + 1) }
await poll(
async () => (await state()).channels.music.status === 'playing',
'assigned music plays',
)
const beforeOrder = (await state()).channels.music
await evaluate(`document.querySelector('[aria-label="Опустить трек 1"]').click()`)
assert.deepEqual(
library[0].tracks.map((t) => t.ID),
[91, 92],
'draft not published',
)
await clickMusic('Отменить')
await evaluate(`document.querySelector('[aria-label="Опустить трек 1"]').click()`)
await clickMusic('Сохранить порядок')
await poll(
async () => (await state()).channels.music.trackIndex === 1,
'order applied without changing file',
)
const afterOrder = (await state()).channels.music
assert.equal(afterOrder.source.url, beforeOrder.source.url)
assert.equal(afterOrder.operation, beforeOrder.operation)
assert.equal(afterOrder.seekOperation, beforeOrder.seekOperation)
assert.equal(afterOrder.desired, 'playing')
assert.ok(afterOrder.position >= beforeOrder.position)
console.log('SCREEN_MUSIC_PLAN_SMOKE_PASS')
await act({ action: 'logout' })
assert.equal((await evaluate('window.screenBridge.onlineState()')).authenticated, false)
console.log('SCREEN_ONLINE_SMOKE_PASS', JSON.stringify({ polls, requests: requests.length }))