Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/common/test_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1221,3 +1221,30 @@
}
EXPECT_TRUE(onDisk);
}

// PMS: https://pms.uniontech.com/bug-view-44975.html commit: N/A(批次4:git 历史无修复提交,按 PMS 复现步骤 + master d634cbad 代码语义锚定)
// 场景:设置中修改"启动时窗口状态"选项后,向已关闭窗口发送 sigChangeWindowSize 导致异常。
// 锚定语义:Settings 构造中 windowstate→sigChangeWindowSize 的 connect 被 #if 0 禁用
// (settings.cpp 146-149)——选项值变更正常生效,但不得再向任何窗口 poke 信号。
TEST_F(SettingsTest, BUG44975_WindowStateOptionChange_DoesNotPokeWindows)
{
// Arrange: 监听 sigChangeWindowSize(修复前该信号会向已关闭/销毁窗口发送)
QString lastMode;
int pokes = 0;
QObject::connect(Settings::instance(), &Settings::sigChangeWindowSize,
[&lastMode, &pokes](QString mode) {

Check warning on line 1235 in tests/common/test_settings.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Parameter 'mode' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Signal callback outlives its captures

When sigChangeWindowSize is emitted after the test returns, the context-less connection on the suite-wide Settings::instance() invokes a lambda that references the expired lastMode and pokes locals, causing use-after-scope and potentially corrupting memory or crashing the test process.

Give the connection a test-scoped context or disconnect it before the captured locals leave scope.

Prompt for AI agents
In `tests/common/test_settings.cpp` at line 1235:

**Signal callback outlives its captures**

When `sigChangeWindowSize` is emitted after the test returns, the context-less connection on the suite-wide `Settings::instance()` invokes a lambda that references the expired `lastMode` and `pokes` locals, causing use-after-scope and potentially corrupting memory or crashing the test process.

Give the connection a test-scoped context or disconnect it before the captured locals leave scope.

lastMode = mode;
++pokes;
});
auto option = s_instance->settings->option("advance.window.windowstate");
ASSERT_NE(option, nullptr);

// Act: 在设置中切换窗口启动状态(PMS 步骤:修改该选项)
option->setValue(QString("fullscreen"));
QApplication::processEvents();

// Assert: 选项值正常更新;无窗口 poke 信号发出
EXPECT_EQ(option->value().toString(), QString("fullscreen"));
EXPECT_EQ(pokes, 0);
EXPECT_TRUE(lastMode.isEmpty());
}
6 changes: 2 additions & 4 deletions tests/common2/test_fileloadthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,7 @@ TEST_F(FileLoadThreadTest, BUG184107_BadAllocDuringRead_EmitsErrorNoCrash)
EXPECT_TRUE(spy.at(0).at(2).toBool());
EXPECT_FALSE(spy.at(0).at(3).toBool());
}
// 当前代码:catch 分支提前 return(:88),deleteLater 仅在正常路径末尾(:132)注册,
// 故此处 guard 未清空属预期;手动补 deleteLater 防泄漏后断言回收
t->deleteLater();
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
// 当前代码(master a66591ed):catch 分支已自带 quit()+deleteLater(),
// runSync 的 DeferredDelete 派发即完成回收,无需(也不得)再手动补删
EXPECT_TRUE(guard.isNull());
}
71 changes: 71 additions & 0 deletions tests/editor_core/test_dtextedit_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1344,3 +1344,74 @@ TEST_F(TextEditTest, BUG60989_UpdateFont_SelectAllInView_NoHang)
EXPECT_TRUE(cur.hasSelection());
EXPECT_LT(cur.selectionEnd() - cur.selectionStart(), edit->document()->characterCount());
}

// PMS: https://pms.uniontech.com/bug-view-46081.html commit: N/A(批次4:git 历史无修复提交,按 PMS 复现步骤 + master d634cbad 代码语义锚定)
// 场景:alt+鼠标列选择模式下,复制/剪切/粘贴异常(列选区内容未按行拼接进入剪贴板)。
// 锚定语义:TextEdit::copy(true) 的 m_bIsAltMod 分支将各列选区文本按 "\n" 拼接写入剪贴板;
// 文本保持不变、列选区保留(copy 不破坏选择状态)。
TEST_F(TextEditTest, BUG46081_ColumnSelectionCopy_JoinsLines)
{
// Arrange: 三行各造一个列选区(每行前 2 字符),进入列编辑模式
setDocText(QString("aa\nbb\ncc\n"));
QList<QTextEdit::ExtraSelection> sels;
for (int line = 0; line < 3; ++line) {
QTextCursor cur(edit->document());
const int blockPos = edit->document()->findBlockByNumber(line).position();
cur.setPosition(blockPos);
cur.setPosition(blockPos + 2, QTextCursor::KeepAnchor);
QTextEdit::ExtraSelection sel;
sel.cursor = cur;
sels << sel;
}
edit->restoreColumnEditSelection(sels);
edit->m_bIsAltMod = true;

// Act: 列复制(ignoreCheck=true 绕开权限校验,聚焦列分支)
edit->copy(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Column cut has no regression case

When column-selection cut is broken while copy and paste continue to work, a defect limited to column-selection cut remains undetected because the new cases exercise copy(true) and paste(), but never invoke cut or verify its text and clipboard effects. Add a column-cut case with assertions for both.

Add a column-selection cut test that checks the clipboard contents and the resulting document text.

Prompt for AI agents
In `tests/editor_core/test_dtextedit_edit.cpp` at line 1370:

**Column cut has no regression case**

When column-selection cut is broken while copy and paste continue to work, a defect limited to column-selection cut remains undetected because the new cases exercise `copy(true)` and `paste()`, but never invoke cut or verify its text and clipboard effects. Add a column-cut case with assertions for both.

Add a column-selection cut test that checks the clipboard contents and the resulting document text.


// Assert: 剪贴板为各选区按行拼接;文本未被改动;列选区仍在
EXPECT_EQ(QApplication::clipboard()->text(), QString("aa\nbb\ncc"));
EXPECT_EQ(edit->toPlainText(), QString("aa\nbb\ncc\n"));
EXPECT_EQ(edit->m_altModSelections.size(), 3);
EXPECT_TRUE(edit->m_bIsAltMod);
}

// PMS: https://pms.uniontech.com/bug-view-46081.html commit: N/A(批次4:无修复提交,按 PMS 步骤 + master 代码语义锚定)
// 场景:alt 列选择后粘贴,粘贴内容应作用于各列选区且可整体撤销。
// 锚定语义:TextEdit::paste() 的 m_bIsAltMod 分支走 insertColumnEditTextEx →
// InsertTextUndoCommand(列路径):每个列选区替换为剪贴板文本(多选区共享同一插入文本,
// InsertTextUndoCommand::redo 列分支语义);undo_ 后完整还原文本与列选区。
TEST_F(TextEditTest, BUG46081_ColumnPaste_ReplacesEachSelectionAndUndoRestores)
{
// Arrange: 三行各造列选区(每行首字符),剪贴板单行文本
setDocText(QString("xx\nyy\nzz\n"));
QList<QTextEdit::ExtraSelection> sels;
for (int line = 0; line < 3; ++line) {
QTextCursor cur(edit->document());
const int blockPos = edit->document()->findBlockByNumber(line).position();
cur.setPosition(blockPos);
cur.setPosition(blockPos + 1, QTextCursor::KeepAnchor);
QTextEdit::ExtraSelection sel;
sel.cursor = cur;
sels << sel;
}
edit->restoreColumnEditSelection(sels);
edit->m_bIsAltMod = true;
QApplication::clipboard()->setText(QStringLiteral("1"));

// Act: 列粘贴
edit->paste();

// Assert: 每个选区被剪贴板文本替换
EXPECT_EQ(edit->toPlainText(), QString("1x\n1y\n1z\n"));
EXPECT_EQ(edit->m_altModSelections.size(), 3);

// Act: 撤销整次列粘贴
edit->undo_();

// Assert: 文本与列选区完整还原(可逆)
EXPECT_EQ(edit->toPlainText(), QString("xx\nyy\nzz\n"));
ASSERT_EQ(edit->m_altModSelections.size(), 3);
EXPECT_EQ(edit->m_altModSelections.first().cursor.selectedText(), QString("x"));
EXPECT_EQ(edit->m_altModSelections.last().cursor.selectedText(), QString("z"));
}
41 changes: 41 additions & 0 deletions tests/editor_core/test_dtextedit_mark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1484,3 +1484,44 @@ TEST_F(TextEditTest, BUG331945_UpdateMark_ColumnSelection_ContainedMarksRemoved)
// Assert: 两个标记均被 [0,20] 完全包含 → 同批次移除,索引不错位
EXPECT_TRUE(edit->m_wordMarkSelections.isEmpty());
}

// PMS: https://pms.uniontech.com/bug-view-44591.html commit: N/A(批次4:git 历史无修复提交,按 PMS 复现步骤 + master d634cbad 代码语义锚定)
// 场景:右键菜单 → 颜色标记 → 高亮所有,标记无任何颜色效果。
// 锚定语义:slotSigColorAllSelected(true, color) → isMarkAllLine 以 color.name() 建立
// MarkAllMatch 记录 + m_mapKeywordMarkSelections[选中文本] 全文匹配选区(格式背景色为所选
// 颜色)→ renderAllSelections 渲染进 extraSelections。
TEST_F(TextEditTest, BUG44591_ColorMarkAll_AppliesChosenColor)
{
// Arrange: 文本含 3 处目标词,选中第一处;直连颜色面板选择信号
setDocText(QString("cat dog cat bird cat"));
QTextCursor cur(edit->document());
cur.setPosition(0);
cur.setPosition(3, QTextCursor::KeepAnchor); // 选中 "cat"
edit->setTextCursor(cur);
const QColor chosen(255, 127, 80);

// Act: 颜色面板确认(高亮所有选中内容)
edit->slotSigColorAllSelected(true, chosen);

// Assert: 操作记录携带所选颜色,类型为 MarkAllMatch
ASSERT_FALSE(edit->m_markOperations.isEmpty());
const auto &op = edit->m_markOperations.last().first;
EXPECT_EQ(op.type, TextEdit::MarkAllMatch);
EXPECT_EQ(op.color, chosen.name());
EXPECT_EQ(op.matchText, QString("cat"));

// Assert: 关键词标记映射建立,全部匹配选区背景色为所选颜色
ASSERT_TRUE(edit->m_mapKeywordMarkSelections.contains(QString("cat")));
const auto marked = edit->m_mapKeywordMarkSelections.value(QString("cat"));
EXPECT_EQ(marked.size(), 3);
for (const auto &pair : marked)
EXPECT_EQ(pair.first.format.background().color(), chosen);

// Assert: 渲染结果(extraSelections)实际携带该背景色(修复前无颜色效果)
bool rendered = false;
for (const auto &es : edit->extraSelections()) {
if (es.format.background().color() == chosen)
rendered = true;
}
EXPECT_TRUE(rendered);
}
140 changes: 140 additions & 0 deletions tests/startmanager/test_startmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,13 @@
// - createWindowFromWrapper 拖拽 pixmap 非空分支(B56 true 侧):QPixmap::rect 虚调用
// 需真实 GUI 像素数据,QCoreApplication 语境不可安全构造,记录为限制

#include <gtest/gtest.h>

Check warning on line 168 in tests/startmanager/test_startmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <gtest/gtest.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include "stubext.h"

Check warning on line 169 in tests/startmanager/test_startmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "stubext.h" not found.

#include <deque>

Check warning on line 171 in tests/startmanager/test_startmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <deque> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QCoreApplication>

Check warning on line 172 in tests/startmanager/test_startmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCoreApplication> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTemporaryDir>

Check warning on line 173 in tests/startmanager/test_startmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTemporaryDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTimer>

Check warning on line 174 in tests/startmanager/test_startmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTimer> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QEventLoop>
#include <QElapsedTimer>
#include <QThread>
Expand Down Expand Up @@ -2757,3 +2758,142 @@
EXPECT_EQ(pendingCalls, 2);
EXPECT_EQ(pendingPaths, QStringList({ lazyB, lazyC }));
}

// PMS: https://pms.uniontech.com/bug-view-55533.html commit: N/A(批次4:git 历史无修复提交,按 PMS 复现步骤 + master d634cbad 代码语义锚定)
// 场景:关闭其中一个窗口后,应用直接退出。
// 锚定语义:slotCloseWindow 仅当窗口列表清空才走清退(unregister + 延迟 quit);
// 多窗口关闭其一 → 仅移除该窗口,剩余窗口完好,绝不调度退出/注销总线。
TEST_F(StartManagerTest, BUG55533_CloseOneOfTwoWindows_RemainingIntact_NoQuit)
{
// Arrange:两个窗口,关闭其一
Window *winA = qobjFake<Window>();
Window *winB = qobjFake<Window>();
obj->m_windows << winA << winB;
stub.set_lamda(static_cast<QObject *(QObject::*)() const>(&QObject::sender),
[winB](const QObject *) -> QObject * { return winB; });
curPathOverride = tmp->path();

// Act
obj->slotCloseWindow();

// Assert:剩余窗口完好;未注销 DBus 总线、未调度退出(含延迟任务也未触发)
ASSERT_EQ(obj->m_windows.count(), 1);
EXPECT_EQ(obj->m_windows.at(0), winA);
EXPECT_EQ(unregisterCalls, 0);
EXPECT_EQ(lastUnregisterService, QString());
EXPECT_EQ(quitCalls, 0);
processEventsFor(1200); // 即便误调度 1000ms 延迟退出也会在此暴露
EXPECT_EQ(quitCalls, 0);
}

// PMS: https://pms.uniontech.com/bug-view-177757.html commit: N/A(批次4:无修复提交,按 PMS 步骤 + master 代码语义锚定)
// 场景:多个窗口被快速连续关闭(用户连按 alt+F4),偶发应用异常。
// 锚定语义:逐个移除仅移除对应窗口;只有最后一次(列表清空)进入清退分支——
// 恰好一次 unregister、恰好一次延迟 quit 调度,不重复调度、不闪退。
TEST_F(StartManagerTest, BUG177757_RapidSequentialClose_SingleQuitScheduling)
{
// Arrange:三个窗口 + 发送者队列(模拟快速连关)
std::deque<Window *> senders;
Window *w1 = qobjFake<Window>();
Window *w2 = qobjFake<Window>();
Window *w3 = qobjFake<Window>();
senders.push_back(w1);
senders.push_back(w2);
senders.push_back(w3);
obj->m_windows << w1 << w2 << w3;
stub.set_lamda(static_cast<QObject *(QObject::*)() const>(&QObject::sender),
[&senders](const QObject *) -> QObject * {
Window *w = senders.front();
senders.pop_front();
return w;
});
curPathOverride = tmp->path();

// Act:快速连续关闭三个窗口(中间不跑事件循环)
obj->slotCloseWindow();
obj->slotCloseWindow();
obj->slotCloseWindow();

// Assert:前两次仅移除;末次清退恰好一次注销 + 一次延迟退出
EXPECT_TRUE(obj->m_windows.isEmpty());
EXPECT_EQ(unregisterCalls, 1);
EXPECT_EQ(lastUnregisterService, QString("com.deepin.Editor"));
EXPECT_EQ(quitCalls, 0); // 延迟 1000ms 尚未触发
processEventsFor(1200);
EXPECT_EQ(quitCalls, 1); // 仅一次退出调度(重复调度会 >1)
}

// PMS: https://pms.uniontech.com/bug-view-49292.html commit: N/A(批次4:无修复提交,按 PMS 步骤 + master 代码语义锚定)
// 场景:重复开关后无法打开(总线名 com.deepin.Editor 被残留进程占用)。
// 锚定语义:末窗关闭即先注销总线名再延迟退出(startmanager.cpp 末窗清退分支)——
// unregisterService("com.deepin.Editor") 先于 quit 发生,让下一次启动能成功注册。
TEST_F(StartManagerTest, BUG49292_LastClose_FreesBusBeforeQuit)
{
// Arrange:单窗口关闭进入清退;桩内记录事件顺序
Window *win = qobjFake<Window>();
obj->m_windows << win;
stub.set_lamda(static_cast<QObject *(QObject::*)() const>(&QObject::sender),
[win](const QObject *) -> QObject * { return win; });
curPathOverride = tmp->path();
QStringList order;
int unregCalls = 0;
stub.set_lamda(
static_cast<bool (QDBusConnection::*)(const QString &)>(&QDBusConnection::unregisterService),
[&order, &unregCalls](QDBusConnection *, const QString &svc) -> bool {
order << QStringLiteral("unregister:") + svc;
++unregCalls;
return true;
});
stub.set_lamda(&QCoreApplication::quit, [&order]() -> void {
order << QStringLiteral("quit");
});

// Act
obj->slotCloseWindow();

// Assert:注销先于退出;总线名正确释放一次(新进程可注册)
EXPECT_TRUE(obj->m_windows.isEmpty());
EXPECT_EQ(unregCalls, 1);
processEventsFor(1200);
EXPECT_EQ(order, QStringList({ QStringLiteral("unregister:com.deepin.Editor"),
QStringLiteral("quit") }));
}

// PMS: https://pms.uniontech.com/bug-view-177725.html commit: N/A(批次4:无修复提交,按 PMS 步骤 + master 代码语义锚定)
// 场景:设置中取消勾选"保留页签"后打开文件,打开的是新建文本而非文件内容。
// 锚定语义:openFilesInTab 带文件参数且无窗口 → createWindow(true) + showCenterWindow +
// 延迟 50ms recoverFile(无临时记录时恢复 0 个) + window->addTab(resolvedFile)——
// 文件参数路径必须落为文件 tab,不能退化为空白新建。
TEST_F(StartManagerTest, BUG177725_KeepTabsDisabled_OpenFile_OpensFileTab)
{
// Arrange:无窗口 + 无临时记录(保留页签已取消)+ 带文件参数
obj->m_qlistTemFile = QStringList(); // 无临时记录
const QString file = tmp->filePath("keep_off.txt");
Window *newWin = qobjFake<Window>();
int recoverCalls = 0;
int blankCalls = 0;
QString addedPath;
stub.set_lamda(static_cast<Window *(StartManager::*)(bool)>(&StartManager::createWindow),
[newWin](StartManager *, bool) -> Window * { return newWin; });
stubWindowInteraction(newWin);
stub.set_lamda(static_cast<int (StartManager::*)(Window *)>(&StartManager::recoverFile),
[&recoverCalls](StartManager *, Window *) -> int {
++recoverCalls;
return 0;
});
stub.set_lamda(static_cast<void (Window::*)()>(&Window::addBlankTab),
[&blankCalls](Window *) { ++blankCalls; });
stub.set_lamda(static_cast<void (Window::*)(const QString &, bool)>(&Window::addTab),
[&addedPath](Window *, const QString &p, bool) { addedPath = p; });

// Act
obj->openFilesInTab(QStringList { file });
processEventsFor(100); // 触发 50ms 延迟打开

// Assert:文件 tab 被添加(非空白新建);恢复空转一次
EXPECT_EQ(showCenterCalls, 1);
EXPECT_TRUE(lastCenterFlag);
EXPECT_EQ(recoverCalls, 1);
EXPECT_EQ(blankCalls, 0);
EXPECT_EQ(addedPath, file);
}
Loading
Loading