diff --git a/sakura_core/_main/CControlTray.cpp b/sakura_core/_main/CControlTray.cpp index 22236d2c19..4b49917899 100644 --- a/sakura_core/_main/CControlTray.cpp +++ b/sakura_core/_main/CControlTray.cpp @@ -96,21 +96,21 @@ void CControlTray::DoGrep() m_cDlgGrep.m_strText = m_pShareData->m_sSearchKeywords.m_aSearchKeys[0]; } if( 0 < m_pShareData->m_sSearchKeywords.m_aGrepFiles.size() ){ - wcscpy( m_cDlgGrep.m_szFile, m_pShareData->m_sSearchKeywords.m_aGrepFiles[0] ); /* 検索ファイル */ + m_cDlgGrep.m_szFile = m_pShareData->m_sSearchKeywords.m_aGrepFiles[0]; /* 検索ファイル */ } if( 0 < m_pShareData->m_sSearchKeywords.m_aGrepFolders.size() ){ - wcscpy( m_cDlgGrep.m_szFolder, m_pShareData->m_sSearchKeywords.m_aGrepFolders[0] ); /* 検索フォルダー */ + m_cDlgGrep.m_szFolder = m_pShareData->m_sSearchKeywords.m_aGrepFolders[0]; /* 検索フォルダー */ } if (0 < m_pShareData->m_sSearchKeywords.m_aExcludeFiles.size()) { - wcscpy(m_cDlgGrep.m_szExcludeFile, m_pShareData->m_sSearchKeywords.m_aExcludeFiles[0]); /* 除外ファイル */ + m_cDlgGrep.m_szExcludeFile = m_pShareData->m_sSearchKeywords.m_aExcludeFiles[0]; /* 除外ファイル */ } if (0 < m_pShareData->m_sSearchKeywords.m_aExcludeFolders.size()) { - wcscpy(m_cDlgGrep.m_szExcludeFolder, m_pShareData->m_sSearchKeywords.m_aExcludeFolders[0]); /* 除外フォルダー */ + m_cDlgGrep.m_szExcludeFolder = m_pShareData->m_sSearchKeywords.m_aExcludeFolders[0]; /* 除外フォルダー */ } /* Grepダイアログの表示 */ - int nRet = m_cDlgGrep.DoModal( m_hInstance, nullptr, L"" ); - if( !nRet || GetTrayHwnd() == nullptr ){ + if (const auto nRet = m_cDlgGrep.DoModal(m_hInstance, nullptr, L""); + !nRet || GetTrayHwnd() == nullptr ){ return; } m_nCurSearchKeySequence = GetDllShareData().m_Common.m_sSearch.m_nSearchKeySequence; @@ -384,7 +384,6 @@ LRESULT CControlTray::DispatchEvent( int nId; HWND hwndWork; - LPHELPINFO lphi; int nRowNum; EditNode* pEditNodeArr; @@ -461,7 +460,7 @@ LRESULT CControlTray::DispatchEvent( case MYWM_HTMLHELP: { auto &sWorkBuffer = m_pShareData->m_sWorkBuffer; - WCHAR* pWork = sWorkBuffer.GetWorkBuffer(); + const auto pWork = sWorkBuffer.GetWorkBuffer(); // pszHelpFile取得 const WCHAR* pszHelpFile = pWork; @@ -470,6 +469,8 @@ LRESULT CControlTray::DispatchEvent( // pszKeywords取得 const WCHAR* pszKeywords = &pWork[cchHelpFile + 1]; + if (!*pszHelpFile) return 0L; + // Jul. 6, 2001 genta HtmlHelpの呼び出し方法変更 hwndHtmlHelp = OpenHtmlHelp( nullptr, @@ -564,13 +565,8 @@ LRESULT CControlTray::DispatchEvent( // case WM_QUERYENDSESSION: case WM_HELP: - lphi = (LPHELPINFO) lParam; - switch( lphi->iContextType ){ - case HELPINFO_MENUITEM: + if (const auto lphi = (LPHELPINFO) lParam; lphi && HELPINFO_MENUITEM == lphi->iContextType) { MyWinHelp( hwnd, HELP_CONTEXT, FuncID_To_HelpContextID( (EFunctionCode)lphi->iCtrlId ) ); - break; - default: - break; } return TRUE; case WM_COMMAND: @@ -1442,24 +1438,23 @@ void CControlTray::TerminateApplication( HWND hWndFrom //!< [in] 呼び出し元のウィンドウハンドル ) { - DLLSHAREDATA* pShareData = &GetDllShareData(); /* 共有データ構造体のアドレスを返す */ + const auto pShareData = &GetDllShareData(); /* 共有データ構造体のアドレスを返す */ /* 現在の編集ウィンドウの数を調べる */ - if( pShareData->m_Common.m_sGeneral.m_bExitConfirm ){ //終了時の確認 - if( 0 < CAppNodeGroupHandle(0).GetEditorWindowsNum() ){ - if( IDYES != ::MYMESSAGEBOX( + if (pShareData->m_Common.m_sGeneral.m_bExitConfirm && //終了時の確認 + 0 < CAppNodeGroupHandle(0).GetEditorWindowsNum() && + IDYES != ::MessageBoxF( hWndFrom, MB_YESNO | MB_APPLMODAL | MB_ICONQUESTION, GSTR_APPNAME, LS(STR_TRAY_EXITALL) ) ){ return; - } - } } + /* 「すべてのウィンドウを閉じる」要求 */ //Oct. 7, 2000 jepro 「編集ウィンドウの全終了」という説明を左記のように変更 - BOOL bCheckConfirm = (pShareData->m_Common.m_sGeneral.m_bExitConfirm)? FALSE: TRUE; // 2006.12.25 ryoji 終了確認済みならそれ以上は確認しない - if( CloseAllEditor( bCheckConfirm, hWndFrom, TRUE, 0 ) ){ // 2006.12.25, 2007.02.13 ryoji 引数追加 + if (const auto bCheckConfirm = pShareData->m_Common.m_sGeneral.m_bExitConfirm; // 2006.12.25 ryoji 終了確認済みならそれ以上は確認しない + CloseAllEditor(bCheckConfirm, hWndFrom, TRUE, 0)) { // 2006.12.25, 2007.02.13 ryoji 引数追加 ::PostMessageAny( pShareData->m_sHandles.m_hwndTray, WM_CLOSE, 0, 0 ); } return; @@ -1506,7 +1501,6 @@ int CControlTray::CreatePopUpMenu_L( void ) WCHAR szMenu[100 + MAX_PATH * 2]; // Jan. 19, 2001 genta POINT po; RECT rc; - EditInfo* pfi; //本当はセマフォにしないとだめ if( m_bUseTrayMenu ) return -1; @@ -1568,7 +1562,7 @@ int CControlTray::CreatePopUpMenu_L( void ) if( IsSakuraMainWindow( m_pShareData->m_sNodes.m_pEditArr[i].GetHwnd() ) ){ /* トレイからエディタへの編集ファイル名要求通知 */ ::SendMessage( m_pShareData->m_sNodes.m_pEditArr[i].GetHwnd(), MYWM_GETFILEINFO, 0, 0 ); - pfi = (EditInfo*)&m_pShareData->m_sWorkBuffer.m_EditInfo_MYWM_GETFILEINFO; + const auto pfi = &m_pShareData->m_sWorkBuffer.m_EditInfo_MYWM_GETFILEINFO; // メニューラベル。1からアクセスキーを振る CFileNameManager::getInstance()->GetMenuFullLabel_WinList( szMenu, int(std::size(szMenu)), pfi, m_pShareData->m_sNodes.m_pEditArr[i].m_nId, i, dcFont.GetHDC() ); diff --git a/sakura_core/_main/CControlTray.h b/sakura_core/_main/CControlTray.h index 69152ab63d..a008e876fa 100644 --- a/sakura_core/_main/CControlTray.h +++ b/sakura_core/_main/CControlTray.h @@ -128,6 +128,7 @@ class CControlTray bool OnAddTypeSetting(size_t index); bool OnDelTypeSetting(size_t index); +public: // テストできないのでアクセス権変更 /* || メンバ変数 */ diff --git a/sakura_core/env/CFileNameManager.h b/sakura_core/env/CFileNameManager.h index 4d3c8939d7..0f6c86958d 100644 --- a/sakura_core/env/CFileNameManager.h +++ b/sakura_core/env/CFileNameManager.h @@ -4,7 +4,7 @@ */ /* Copyright (C) 2008, kobake - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization SPDX-License-Identifier: Zlib */ @@ -22,15 +22,10 @@ struct EditInfo; DLLSHAREDATA& GetDllShareData(); //!ファイル名管理 -class CFileNameManager : public TSingleton{ - friend class TSingleton; - CFileNameManager() - { - m_pShareData = &GetDllShareData(); - m_nTransformFileNameCount = -1; - } - +class CFileNameManager : public TSakuraSingleton { public: + CFileNameManager() = default; + //ファイル名関連 LPWSTR GetTransformFileNameFast( LPCWSTR, LPWSTR, int nDestLen, HDC hDC, bool bFitMode = true, int cchMaxWidth = 0 ); // 2002.11.24 Moca Add int TransformFileName_MakeCache( void ); @@ -60,11 +55,12 @@ class CFileNameManager : public TSingleton{ static WCHAR GetAccessKeyByIndex(int index, bool bZeroOrigin); private: - DLLSHAREDATA* m_pShareData; + DLLSHAREDATA* m_pShareData = &GetDllShareData(); // ファイル名簡易表示用キャッシュ - int m_nTransformFileNameCount; // 有効数 + int m_nTransformFileNameCount = -1; // 有効数 WCHAR m_szTransformFileNameFromExp[MAX_TRANSFORM_FILENAME][_MAX_PATH]; int m_nTransformFileNameOrgId[MAX_TRANSFORM_FILENAME]; }; + #endif /* SAKURA_CFILENAMEMANAGER_2B89B426_470E_40D6_B62E_5321E383ECD6_H_ */ diff --git a/sakura_core/env/CShareData.cpp b/sakura_core/env/CShareData.cpp index ea55f86a75..0b9e4a48cd 100644 --- a/sakura_core/env/CShareData.cpp +++ b/sakura_core/env/CShareData.cpp @@ -26,7 +26,9 @@ #include "StdAfx.h" #include "env/CShareData.h" + #include "env/DLLSHAREDATA.h" +#include "env/CFileNameManager.h" #include "env/CShareData_IO.h" #include "env/CSakuraEnvironment.h" #include "doc/CDocListener.h" // SLoadInfo @@ -73,6 +75,8 @@ CShareData::CShareData() = default; */ CShareData::~CShareData() { + CFileNameManager::resetInstance(); + if( m_pShareData ){ /* プロセスのアドレス空間から、 すでにマップされているファイル ビューをアンマップします */ ::UnmapViewOfFile( m_pShareData ); diff --git a/sakura_core/util/design_template.h b/sakura_core/util/design_template.h index aaf7c53430..55f40e405a 100644 --- a/sakura_core/util/design_template.h +++ b/sakura_core/util/design_template.h @@ -7,7 +7,7 @@ */ /* Copyright (C) 2008, kobake - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization SPDX-License-Identifier: Zlib */ @@ -16,7 +16,10 @@ #pragma once #include +#include +#include #include +#include #include #include "debug/Debug2.h" @@ -49,6 +52,63 @@ class TSingleton{ DISALLOW_COPY_AND_ASSIGN(TSingleton); }; +/*! + * @brief サクラエディタの変則Singletonパターン。 + * + * TSingletonを元に作成。 + * 生成したインスタンスをリセットできるようにしてある。 + */ +template +class TSakuraSingleton { +private: + using Me = TSakuraSingleton; + + //! 生成済みインスタンス + static inline std::unique_ptr gm_Instance = nullptr; + +public: + /*! + * @brief インスタンスポインタを取得する + * + * @returns インスタンスポインタ + * @note インスタンスが未生成の場合は生成する + */ + [[nodiscard]] + static T* getInstance() + { + static_assert( + std::default_initializable && !std::is_array_v, + "T must be publicly default-initializable and must not be an array" + ); + + if (!gm_Instance) { + gm_Instance = std::make_unique(); + } + + return gm_Instance.get(); + } + + /*! + * @brief インスタンスを破棄する + * + * @note 取得したポインタを破棄後に使用しないこと + */ + static void resetInstance() + { + gm_Instance.reset(); + } + +protected: + TSakuraSingleton() = default; + +public: + TSakuraSingleton(const Me&) = delete; + Me& operator = (const Me&) = delete; + + TSakuraSingleton(Me&&) = delete; + Me& operator = (Me&&) = delete; +}; + /*! 複数インスタンスを生成しようとしたときのエラー */ diff --git a/src/test/cpp/tests1/env/ShareDataTestSuite.cpp b/src/test/cpp/tests1/env/ShareDataTestSuite.cpp index 55db30f33d..7ebfb5a9d5 100644 --- a/src/test/cpp/tests1/env/ShareDataTestSuite.cpp +++ b/src/test/cpp/tests1/env/ShareDataTestSuite.cpp @@ -14,6 +14,9 @@ namespace env { */ /* static */ void ShareDataTestSuite::SetUpShareData() { + // 言語環境を初期化する + CSelectLang::InitializeLanguageEnvironment(); + pcShareData = std::make_unique(); EXPECT_THAT(pcShareData->InitShareData(), IsTrue()); diff --git a/src/test/cpp/tests1/test-design_template.cpp b/src/test/cpp/tests1/test-design_template.cpp index a281ec0e99..a03339b107 100644 --- a/src/test/cpp/tests1/test-design_template.cpp +++ b/src/test/cpp/tests1/test-design_template.cpp @@ -11,6 +11,32 @@ #include "util/design_template.h" +/*! + * TSakuraSingletonの挙動を検証するためのクラス + */ +class CSakuraSingleton : public TSakuraSingleton +{ +public: + CSakuraSingleton() = default; + virtual ~CSakuraSingleton() noexcept = default; +}; + +/*! + * @brief TSakuraSingletonの挙動を検証するテスト + */ +TEST(CSakuraSingleton, CSakuraSingleton) +{ + // いきなり呼び出してもNULLは返らない + EXPECT_THAT(CSakuraSingleton::getInstance(), NotNull()); + + // 何度呼び出しても同じ値が返る + const auto pInstance = CSakuraSingleton::getInstance(); + EXPECT_THAT(CSakuraSingleton::getInstance(), Eq(pInstance)); + EXPECT_THAT(CSakuraSingleton::getInstance(), Eq(pInstance)); + EXPECT_THAT(CSakuraSingleton::getInstance(), Eq(pInstance)); + + CSakuraSingleton::resetInstance(); +} /*! * TSingleInstanceの挙動を検証するためのクラス diff --git a/src/test/cpp/tests1/test-window.cpp b/src/test/cpp/tests1/test-window.cpp index 191f1b6025..ee6ab3a000 100644 --- a/src/test/cpp/tests1/test-window.cpp +++ b/src/test/cpp/tests1/test-window.cpp @@ -54,17 +54,46 @@ using namespace std::literals::string_view_literals; void extract_zip_resource(WORD id, const std::optional& optOutDir); +namespace testing { + +void RequestForeignWindowClose(HWND hWnd); + +} // namespace testing + namespace window { -struct TrayWndTest : public ::testing::Test, public env::ShareDataTestSuite { +struct TrayWndTest : public ::testing::Test, public env::ShareDataTestSuite, public window::UiaTestSuite { using CControlTrayHolder = std::unique_ptr; + static inline CControlTrayHolder pcTrayWnd = nullptr; + /*! * テストスイートの開始前に1回だけ呼ばれる関数 */ static void SetUpTestSuite() { + SetUpUia(); + SetUpShareData(); + + // トレイウィンドウをインスタンス化する + pcTrayWnd = std::make_unique(); + + pcTrayWnd->m_hIcons.Create(G_AppInstance()); + + pcTrayWnd->m_cMenuDrawer.Create( + CSelectLang::getLangRsrcInstance(), + pcTrayWnd->GetTrayHwnd(), + &pcTrayWnd->m_hIcons + ); + + //プロパティ管理 + pcTrayWnd->m_pcPropertyManager = new CPropertyManager(); + pcTrayWnd->m_pcPropertyManager->Create( + pcTrayWnd->GetTrayHwnd(), + &pcTrayWnd->m_hIcons, + &pcTrayWnd->m_cMenuDrawer + ); } /*! @@ -72,28 +101,126 @@ struct TrayWndTest : public ::testing::Test, public env::ShareDataTestSuite { */ static void TearDownTestSuite() { - TearDownShareData(); - } + // トレイウィンドウのインスタンスを破棄する + pcTrayWnd = nullptr; - CControlTrayHolder pcTrayWnd = nullptr; + TearDownShareData(); - /*! - * テストが起動される直前に毎回呼ばれる関数 - */ - void SetUp() override { - // テストクラスをインスタンス化する - pcTrayWnd = std::make_unique(); + TearDownUia(); } /*! * テストが実行された直後に毎回呼ばれる関数 */ void TearDown() override { - // テストクラスのインスタンスを破棄する - pcTrayWnd = nullptr; + // キューに溜まったメッセージは全部捨てる + MSG msg{}; + while (::PeekMessageW(&msg, nullptr, 0L, 0L, PM_REMOVE)) ; } }; +TEST_F(TrayWndTest, OpenNewEditor101) +{ + // 開いているファイルの数を上限値に設定する + GetDllShareData().m_sNodes.m_nEditArrNum = MAX_EDITWINDOWS; + + SLoadInfo sLoadInfo{}; + EXPECT_THAT(CControlTray::OpenNewEditor(nullptr, HWND(nullptr), sLoadInfo), IsFalse()); + + // 設定を元に戻す + GetDllShareData().m_sNodes.m_nEditArrNum = 0; +} + +TEST_F(TrayWndTest, OpenNewEditor102) +{ + // 開いているファイルの数を上限値に設定する + GetDllShareData().m_sNodes.m_nEditArrNum = MAX_EDITWINDOWS; + + EditInfo fi{}; + EXPECT_THAT(CControlTray::OpenNewEditor2(nullptr, HWND(nullptr), &fi, false), IsFalse()); + + // 設定を元に戻す + GetDllShareData().m_sNodes.m_nEditArrNum = 0; +} + +TEST_F(TrayWndTest, DISABLED_OnCreate101) // パラメーター不正の考慮がないので呼べない +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_CREATE, 0L, 0L), IsTrue()); // 戻り値は反転される +} + +TEST_F(TrayWndTest, DISABLED_OnDestroy101) // パラメーター不正の考慮がないので呼べない +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_DESTROY, 0L, 0L), IsFalse()); +} + +TEST_F(TrayWndTest, DISABLED_OnClose101) // パラメーター不正の考慮がないので呼べない +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_CLOSE, 0L, 0L), IsFalse()); +} + +TEST_F(TrayWndTest, DISABLED_OnQueryEndSession101) // パラメーター不正の考慮がないので呼べない +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_QUERYENDSESSION, 0L, 0L), IsTrue()); +} + +TEST_F(TrayWndTest, OnEndSession101) +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_ENDSESSION, FALSE, 0L), IsFalse()); +} + +TEST_F(TrayWndTest, OnHelp101) +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_HELP, 0L, 0L), IsTrue()); + + HELPINFO hi{}; + hi.iContextType = HELPINFO_WINDOW; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_HELP, 0L, LPARAM(&hi)), IsTrue()); +} + +TEST_F(TrayWndTest, OnCommand101) +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_COMMAND, 0L, 0L), IsFalse()); +} + +TEST_F(TrayWndTest, OnTimer101) +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_TIMER, 2 /* IDT_EDITCHECK */, 0L), IsFalse()); +} + +TEST_F(TrayWndTest, OnMenuChar101) +{ + HWND hWndTray = nullptr; + pcTrayWnd->DispatchEvent(hWndTray, WM_MENUCHAR, 0L, 0L); +} + +TEST_F(TrayWndTest, OnExitMenuLoop101) +{ + HWND hWndTray = nullptr; + pcTrayWnd->DispatchEvent(hWndTray, WM_EXITMENULOOP, 0L, 0L); +} + +TEST_F(TrayWndTest, OnHotKey101) +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, WM_HOTKEY, 0L, 0L), IsFalse()); +} + +TEST_F(TrayWndTest, OnTaskbarReCreated101) +{ + const UINT uMsgTaskbarCreated = ::RegisterWindowMessageW(L"TaskbarCreated"); + + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, uMsgTaskbarCreated, 0L, 0L), IsFalse()); +} + TEST_F(TrayWndTest, OnGetTypeSetting001) { // 受け取りバッファに値を設定 @@ -247,6 +374,272 @@ TEST_F(TrayWndTest, OnChangeSetting001) EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, MYWM_CHANGESETTING, 0, int(PM_CHANGESETTING_ALL)), 0); } +TEST_F(TrayWndTest, OnDeleteMe101) +{ + pcTrayWnd->m_bCreatedTrayIcon = true; + + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, MYWM_DELETE_ME, 0L, 0L), IsFalse()); + + pcTrayWnd->m_bCreatedTrayIcon = false; +} + +TEST_F(TrayWndTest, OnHtmlHelp101) +{ + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, MYWM_HTMLHELP, WPARAM(hWndTray), 0L), IsFalse()); +} + +/*! + * トレイ左クリックメニューの表示テスト + * 左クリックメニューからGrepダイアログを表示して実行する + */ +TEST_F(TrayWndTest, DoGrep001) +{ + // 検索条件 + CSearchKeywordManager().AddToSearchKeyArr(LR"(localhost)"); + + // 検索フォルダー + CSearchKeywordManager().AddToGrepFolderArr(LR"(C:\WINDOWS\System32\Drivers)"); + + // 検索ファイル + CSearchKeywordManager().AddToGrepFileArr(LR"(*.*)"); + + // 除外フォルダー + CSearchKeywordManager().AddToExcludeFolderArr(LR"(en-US;DriverData;UMDF;udc;mde;wd;)"); + + // 除外ファイル + CSearchKeywordManager().AddToExcludeFileArr(LR"(*.sys;*.dll;*.exe;*.mui;*.nls;*.chm;*.dat;*.tmp;*.wdf)"); + + // 開いているファイルの数を上限値に設定する + GetDllShareData().m_sNodes.m_nEditArrNum = MAX_EDITWINDOWS; + + // ポップアップメニューは親ウィンドウを指定する必要があるのでダミーウィンドウを作る + const auto hInstance = G_AppInstance(); + const auto hWnd = ::CreateWindowExW(0, WC_STATIC, L"test", 0, 1, 1, 1, 1, HWND(nullptr), HMENU(nullptr), hInstance, nullptr); + using WindowHolder = cxx::ResourceHolder<&::DestroyWindow>; + WindowHolder hWndHolder{ hWnd }; + + pcTrayWnd->m_hInstance = hInstance; + pcTrayWnd->m_hWnd = hWnd; + + // 表示されたGrepダイアログを閉じるためのスレッドを起動する + std::jthread t1 = StartWindowCloser(L"Grep", [this](HWND hWndDlg) { + // 検索ボタンを押下してGrep実行する + EmulateInvokeButton(hWndDlg, L"検索(F)"); + }); + + std::jthread t2([this] { + // ポップアップメニュー項目を選択させる + EmulateSelectPopupMenu(L"Grep(G)..."); + }); + + // トレイアイコン左クリックメニューを表示させる + HWND hWndTray = hWnd; + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONDOWN); + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONUP); + + // ポップアップメニュー項目の選択を待つ + t2.join(); + + // Grepダイアログが閉じられるのを待つ + t1.join(); + + // 設定を元に戻す + GetDllShareData().m_sSearchKeywords.m_aSearchKeys.clear(); + GetDllShareData().m_sSearchKeywords.m_aGrepFolders.clear(); + GetDllShareData().m_sSearchKeywords.m_aGrepFiles.clear(); + GetDllShareData().m_sSearchKeywords.m_aExcludeFolders.clear(); + GetDllShareData().m_sSearchKeywords.m_aExcludeFiles.clear(); + + GetDllShareData().m_sNodes.m_nEditArrNum = 0; +} + +/*! + * トレイ左クリックメニューの表示テスト + * 左クリックメニューから開くダイアログを表示する + */ +TEST_F(TrayWndTest, OpenFile001) +{ + const auto path = GetIniFileName().replace_filename(L"dummy.txt"); + + std::error_code ec; + std::filesystem::remove(path, ec); + + { + std::wofstream fos(path); + fos << L"ダミーファイルです" << std::endl; + } + + // 開いているファイルの数を上限値に設定する + GetDllShareData().m_sNodes.m_nEditArrNum = MAX_EDITWINDOWS; + + // ポップアップメニューは親ウィンドウを指定する必要があるのでダミーウィンドウを作る + const auto hInstance = G_AppInstance(); + const auto hWnd = ::CreateWindowExW(0, WC_STATIC, L"test", 0, 1, 1, 1, 1, HWND(nullptr), HMENU(nullptr), hInstance, nullptr); + using WindowHolder = cxx::ResourceHolder<&::DestroyWindow>; + WindowHolder hWndHolder{ hWnd }; + + pcTrayWnd->m_hInstance = hInstance; + pcTrayWnd->m_hWnd = hWnd; + + // 表示されたファイルダイアログを閉じるためのスレッドを起動する + std::jthread t1([this, path] { + EmulateEnterOpenFileName(path); + }); + + std::jthread t2([this] { + // ポップアップメニュー項目を選択させる + EmulateSelectPopupMenu(L"開く(O)..."); + }); + + // トレイアイコン左クリックメニューを表示させる + HWND hWndTray = hWnd; + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONDOWN); + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONUP); + + // ポップアップメニュー項目の選択を待つ + t2.join(); + + // ファイルダイアログが閉じられるのを待つ + t1.join(); + + // 設定を元に戻す + GetDllShareData().m_sNodes.m_nEditArrNum = 0; + + std::filesystem::remove(path, ec); +} + +/*! + * トレイダブルクリックのテスト + * トレイアイコンをダブルクリックすると新規エディターが開く + */ +TEST_F(TrayWndTest, OpenNewEditor103) +{ + using HandleHolder = cxx::ResourceHolder<&::CloseHandle>; + + // 表示された編集ウィンドウを閉じるためのスレッドを起動する + std::jthread t1([this] { + // 編集ウィンドウハンドルを保存する + const auto hWndFound = WaitForWindow(GSTR_EDITWINDOWNAME, std::nullopt, 5000, false); + + // 編集ウィンドウからプロセスIDを取得する + DWORD dwProcessId = 0; + ::GetWindowThreadProcessId(hWndFound, &dwProcessId); + + // プロセス情報の問い合せを行うためのハンドルを開く + HandleHolder ep{ ::OpenProcess(SYNCHRONIZE, FALSE, dwProcessId) }; + + // 編集ウインドウにクローズを要求する + testing::RequestForeignWindowClose(hWndFound); + + // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ + ::WaitForSingleObject(ep, 30000); + }); + + // ミューテックスの名前を組み立てる + SFilePath szMutexName{ GSTR_MUTEX_SAKURA_CP }; + + // ミューテックスを作成してロックする + HandleHolder hMutex{ ::CreateMutexW(nullptr, TRUE, szMutexName) }; + EXPECT_THAT(hMutex, NotNull()); + + // コマンドラインオブジェクトを用意する + CCommandLine cCommandLine{}; + cCommandLine.ParseCommandLine(L"-PROF=", false); + + // トレイアイコンダブルクリックイベントを発生させる + HWND hWndTray = nullptr; + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONDBLCLK); +} + +/*! + * トレイ右クリックメニューの表示テスト + * 右クリックメニューからバージョン情報ダイアログを表示する + */ +TEST_F(TrayWndTest, ShowDlgAbout001) +{ + // ポップアップメニューは親ウィンドウを指定する必要があるのでダミーウィンドウを作る + const auto hInstance = G_AppInstance(); + const auto hWnd = ::CreateWindowExW(0, WC_STATIC, L"test", 0, 1, 1, 1, 1, HWND(nullptr), HMENU(nullptr), hInstance, nullptr); + using WindowHolder = cxx::ResourceHolder<&::DestroyWindow>; + WindowHolder hWndHolder{ hWnd }; + + pcTrayWnd->m_hInstance = hInstance; + pcTrayWnd->m_hWnd = hWnd; + + // 表示されたバージョン情報ダイアログを閉じるためのスレッドを起動する + std::jthread t1 = StartWindowCloser(LS(F_ABOUT), [this] (HWND hWndDlg) { + // OKボタンを押下して閉じる + EmulateInvokeButton(hWndDlg, L"OK"); + }); + + std::jthread t2([this] { + // ポップアップメニュー項目を選択させる + EmulateSelectPopupMenu(L"バージョン情報(A)"); + }); + + // トレイアイコン右クリックメニューを表示させる + HWND hWndTray = hWnd; + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_RBUTTONDOWN); + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_RBUTTONUP); + + // ポップアップメニュー項目の選択を待つ + t2.join(); + + // バージョン情報ダイアログが閉じられるのを待つ + t1.join(); +} + +/*! + * トレイ左クリックメニューの表示テスト + * 左クリックメニューから履歴とお気に入りの管理ダイアログを表示しする + */ +TEST_F(TrayWndTest, ShowDlgFavorite001) +{ + // ポップアップメニューは親ウィンドウを指定する必要があるのでダミーウィンドウを作る + const auto hInstance = G_AppInstance(); + const auto hWnd = ::CreateWindowExW(0, WC_STATIC, L"test", 0, 1, 1, 1, 1, HWND(nullptr), HMENU(nullptr), hInstance, nullptr); + using WindowHolder = cxx::ResourceHolder<&::DestroyWindow>; + WindowHolder hWndHolder{ hWnd }; + + pcTrayWnd->m_hInstance = hInstance; + pcTrayWnd->m_hWnd = hWnd; + + // 表示された履歴とお気に入りの管理ダイアログを閉じるためのスレッドを起動する + std::jthread t1 = StartWindowCloser(L"履歴とお気に入りの管理", [this](HWND hWndDlg) { + EmulateInvokeButton(hWndDlg, L"閉じる(C)"); + }); + + std::jthread t2([this] { + // ポップアップメニュー項目を選択させる + EmulateSelectPopupMenu(L"履歴の管理(M)..."); + }); + + // トレイアイコン左クリックメニューを表示させる + HWND hWndTray = hWnd; + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONDOWN); + pcTrayWnd->DispatchEvent(hWndTray, MYWM_NOTIFYICON, 0L, WM_LBUTTONUP); + + // ポップアップメニュー項目の選択を待つ + t2.join(); + + // 履歴とお気に入りの管理ダイアログが閉じられるのを待つ + t1.join(); +} + +TEST_F(TrayWndTest, ShowDlgWinList101) +{ + // 表示されたウィンドウ一覧ダイアログを閉じるためのスレッドを起動する + std::jthread t1 = StartWindowCloser(LS(F_WINDOW_LIST_SUBMENU), [this] (HWND hWndDlg) { + // OKボタンを押下して閉じる + EmulateInvokeButton(hWndDlg, L"OK"); + }); + + HWND hWndTray = nullptr; + EXPECT_THAT(pcTrayWnd->DispatchEvent(hWndTray, MYWM_DLGWINLIST, 0L, 0L), IsFalse()); +} + struct EditWndTest : public ::testing::Test, public window::EditorTestSuite, public window::UiaTestSuite { static constexpr HINSTANCE unusedArg1 = nullptr; diff --git a/src/test/cpp/tests1/window/EditorTestSuite.cpp b/src/test/cpp/tests1/window/EditorTestSuite.cpp index 18347c2eac..fdbc456228 100644 --- a/src/test/cpp/tests1/window/EditorTestSuite.cpp +++ b/src/test/cpp/tests1/window/EditorTestSuite.cpp @@ -16,9 +16,6 @@ namespace window { */ /* static */ void EditorTestSuite::SetUpEditor() { - // 言語環境を初期化する - CSelectLang::InitializeLanguageEnvironment(); - SetUpShareData(); // CanBeMoveリージョンをテストケースに分割する。(すぐ対応できないのでコメント残し) diff --git a/src/test/cpp/tests1/window/UiaTestSuite.hpp b/src/test/cpp/tests1/window/UiaTestSuite.hpp index 3177a9471c..c340fb6c5d 100644 --- a/src/test/cpp/tests1/window/UiaTestSuite.hpp +++ b/src/test/cpp/tests1/window/UiaTestSuite.hpp @@ -116,6 +116,33 @@ struct UiaTestSuite EXPECT_THAT(SendInput(inputs), Eq(std::size(inputs))); } + void EmulateSelectPopupMenu(const _bstr_t& name) const + { + const auto hPopupMenu = WaitForWindow(MAKEINTRESOURCEW(32768), std::nullopt, 60000, false); + EXPECT_THAT(hPopupMenu, NotNull()); + + auto condMenuItem = CreatePropertyCondition( + UIA_ControlTypePropertyId, + UIA_MenuItemControlTypeId + ); + auto condName = CreatePropertyCondition( + UIA_NamePropertyId, + name + ); + auto cond = CreateAndCondition(condMenuItem, condName); + + auto item = FindFirst(hPopupMenu, TreeScope_Subtree, cond); + EXPECT_THAT(item, NotNull()); + + cxx::com_pointer invoke; + EXPECT_HRESULT_SUCCEEDED(item->GetCurrentPatternAs( + UIA_InvokePatternId, + IID_PPV_ARGS(&invoke) + )); + + EXPECT_HRESULT_SUCCEEDED(invoke->Invoke()); + } + IUIAutomationElementPtr FindFirst( _In_ HWND hWndDlg, TreeScope scope,