From 9d2ddcc496a457788506321fcecf53fd24216e87 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 20:04:30 +0100 Subject: [PATCH] Reproduce stale stream captions without restarting playback --- tools/chatgui/tests/test_stream_view.cpp | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tools/chatgui/tests/test_stream_view.cpp b/tools/chatgui/tests/test_stream_view.cpp index ace90a0b..5d3e5071 100644 --- a/tools/chatgui/tests/test_stream_view.cpp +++ b/tools/chatgui/tests/test_stream_view.cpp @@ -566,6 +566,82 @@ private slots: #endif } + void gridRenamesPreservePlayback_data() { + QTest::addColumn("name"); + QTest::newRow("ordinary") << QString("Renamed source"); + QTest::newRow("unicode") << QString::fromUtf8("Новое имя 👩‍💻 «Источник»"); + QTest::newRow("empty-name") << QString(); + } + + void gridRenamesPreservePlayback() { +#if defined(TEST_NULL_AUDIO) && defined(HAVE_FFMPEG) && defined(HAVE_STREAM_CAPTURE) + QFETCH(QString, name); + if (publisher) { QCOMPARE(stream_publish_finish(publisher), STREAM_OK); stream_close(publisher); publisher = nullptr; } + QCOMPARE(stream_publish_open(inst, group, nullptr, &publisher), STREAM_OK); + stream_info info{}; QCOMPARE(stream_get_info(publisher, &info), STREAM_OK); epoch = info.descriptor.source_epoch; + QVERIFY(SoundManager::instance()->init()); + StreamGridWindow grid(group); grid.show(); + const uint64_t own = inst->node_id ^ uint64_t(1); + QString ownName = "Own original", remoteName = "Remote original"; + auto snapshot = [&](bool valid = true, uint64_t eventGroup = 0) { + QJsonArray sources{ + QJsonObject{{"id", QString::number(own)}, {"epoch", QString::number(epoch, 16)}, {"name", ownName}, {"self", 1}}, + QJsonObject{{"id", QString::number(inst->node_id)}, {"epoch", QString::number(epoch, 16)}, {"name", remoteName}, {"self", 0}}}; + QByteArray bytes(9, '\0'); stream_write_be(reinterpret_cast(bytes.data()), eventGroup ? eventGroup : group, 8); + bytes[8] = valid; bytes += QJsonDocument(sources).toJson(QJsonDocument::Compact); + grid.onSources(reinterpret_cast(bytes.constData()), bytes.size()); + }; + snapshot(); + QPointer local = grid.findChild(QStringLiteral("streamPreview_%1").arg(own)); QVERIFY(local); + QPointer remote = grid.findChild(); QVERIFY(remote); + QPointer localTitle, remoteTitle; + for (auto* label : local->findChildren()) if (label->text() == ownName) localTitle = label; + for (auto* label : remote->findChildren()) if (label->text() == remoteName) remoteTitle = label; + QVERIFY(localTitle); QVERIFY(remoteTitle); + QPointer preview = local->findChild(); QVERIFY(preview); + QVideoFrame frame(QImage(16, 16, QImage::Format_RGB32)); frame.setStartTime(123000); + grid.setLocalPreview(group, QVariant::fromValue(frame)); + QPointer video = remote->findChild(); QVERIFY(video); + auto* volume = remote->findChild(); QVERIFY(volume); volume->setValue(37); + QTRY_VERIFY(stream_get_info(publisher, &info) == STREAM_OK && info.owners == 2); + QTest::mouseClick(video, Qt::LeftButton); QTRY_COMPARE(grid.focusedSource(), inst->node_id); + QVERIFY(grid.isFullScreen()); QVERIFY(local->isHidden()); + QSignalSpy titles(remote, &QWidget::windowTitleChanged); + ownName = name.isEmpty() ? QString() : name + " / local"; + remoteName = name.isEmpty() ? QString() : name + " / remote"; + const QString expectedLocal = name.isEmpty() ? QString::number(own) : ownName; + const QString expectedRemote = name.isEmpty() ? QString::number(inst->node_id) : remoteName; + snapshot(false); snapshot(true, group ^ (uint64_t(1) << 63)); + QCOMPARE(localTitle->text(), QString("Own original")); QCOMPARE(remoteTitle->text(), QString("Remote original")); + snapshot(); + QCOMPARE(localTitle->text(), expectedLocal); QCOMPARE(localTitle->toolTip(), expectedLocal); + QCOMPARE(remoteTitle->text(), expectedRemote); QCOMPARE(remoteTitle->toolTip(), expectedRemote); + QCOMPARE(remote->windowTitle(), QString("Live stream: %1").arg(expectedRemote)); QCOMPARE(titles.count(), 1); + snapshot(); QCOMPARE(titles.count(), 1); + QVERIFY(local); QVERIFY(remote); QVERIFY(preview); QVERIFY(video); + QCOMPARE(grid.findChild(), remote.data()); QCOMPARE(remote->findChild(), video.data()); + QCOMPARE(preview->videoSink()->videoFrame().startTime(), qint64(123000)); QCOMPARE(volume->value(), 37); + QCOMPARE(grid.focusedSource(), inst->node_id); QVERIFY(grid.isFullScreen()); QVERIFY(local->isHidden()); + QCOMPARE(stream_get_info(publisher, &info), STREAM_OK); QCOMPARE(info.owners, 2u); QCOMPARE(info.descriptor.source_epoch, epoch); + // Неудачный reader должен повторно открыться с последним именем, без сброса соседнего preview. + gui_stream_detach(inst); QTRY_VERIFY(remote->failed()); + remoteName = QString::fromUtf8("После переподключения 🧑‍💻"); snapshot(); + QCOMPARE(remoteTitle->text(), remoteName); QVERIFY(gui_stream_attach(inst)); + QTRY_VERIFY_WITH_TIMEOUT(remote.isNull(), 2500); + auto* retried = grid.findChild(); QVERIFY(retried); + QCOMPARE(retried->windowTitle(), QString("Live stream: %1").arg(remoteName)); + bool currentCaption = false; + for (auto* label : retried->findChildren()) + currentCaption |= label->text() == remoteName && label->toolTip() == remoteName; + QVERIFY(currentCaption); QVERIFY(local); QVERIFY(preview); + QCOMPARE(preview->videoSink()->videoFrame().startTime(), qint64(123000)); + QTRY_VERIFY(stream_get_info(publisher, &info) == STREAM_OK && info.owners == 2); + grid.close(); QTRY_VERIFY(stream_get_info(publisher, &info) == STREAM_OK && info.owners == 1); +#else + QSKIP("Rename fixture requires null audio, FFmpeg and Qt Multimedia"); +#endif + } + void boundedBatchesAndNotifications() { QCOMPARE(stream_publish_finish(publisher), STREAM_OK); stream_close(publisher); publisher = nullptr; QCOMPARE(stream_publish_open(inst, group, nullptr, &publisher), STREAM_OK);