From d04f44c77213b91ba972bf9c4b699b66703192c4 Mon Sep 17 00:00:00 2001 From: ai-kurou Date: Fri, 29 May 2026 17:11:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?RssiThresholdUseCase=E3=81=AE=E3=83=A6?= =?UTF-8?q?=E3=83=8B=E3=83=83=E3=83=88=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/RssiThresholdUseCaseTest.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 core/domain/src/test/java/kurou/androidpods/core/domain/RssiThresholdUseCaseTest.kt diff --git a/core/domain/src/test/java/kurou/androidpods/core/domain/RssiThresholdUseCaseTest.kt b/core/domain/src/test/java/kurou/androidpods/core/domain/RssiThresholdUseCaseTest.kt new file mode 100644 index 0000000..7f361e8 --- /dev/null +++ b/core/domain/src/test/java/kurou/androidpods/core/domain/RssiThresholdUseCaseTest.kt @@ -0,0 +1,52 @@ +package kurou.androidpods.core.domain + +import io.mockk.coVerify +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import io.mockk.verify +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class RssiThresholdUseCaseTest { + private lateinit var useCase: RssiThresholdUseCase + private val repository = mockk(relaxUnitFun = true) + + @Before + fun setUp() { + useCase = RssiThresholdUseCase(repository) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `observeがrepositoryのobserveのFlowを返す`() = + runTest { + val fakeFlow = MutableStateFlow(RssiThreshold.VERY_NEAR) + every { repository.observe() } returns fakeFlow + + val result = useCase.observe().first() + + assertEquals(RssiThreshold.VERY_NEAR, result) + verify(exactly = 1) { repository.observe() } + confirmVerified(repository) + } + + @Test + fun `updateでrepositoryのupdateが呼ばれる`() = + runTest { + useCase.update(RssiThreshold.NEAR) + + coVerify(exactly = 1) { repository.update(RssiThreshold.NEAR) } + confirmVerified(repository) + } +} From c9770fb30d9810862d9dabe4d18a972415901c12 Mon Sep 17 00:00:00 2001 From: ai-kurou Date: Fri, 29 May 2026 17:37:32 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test:=20=E3=83=80=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E3=80=8C?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E7=A2=BA=E8=AA=8D+=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=80=8D=E3=81=A8=E3=80=8C=E3=82=AD?= =?UTF-8?q?=E3=83=A3=E3=83=B3=E3=82=BB=E3=83=AB=E3=80=8D=E3=81=AE2?= =?UTF-8?q?=E3=82=B1=E3=83=BC=E3=82=B9=E3=81=AB=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../onboarding/BluetoothDeniedDialogTest.kt | 15 +++-------- .../BluetoothUnavailableDialogTest.kt | 13 +++------- .../onboarding/PermissionDeniedDialogTest.kt | 15 +++-------- .../settings/OverlayPositionDialogTest.kt | 19 +++----------- .../settings/PermissionRequiredDialogTest.kt | 15 +++-------- .../settings/RssiThresholdDialogTest.kt | 21 ++++++++++------ .../feature/settings/ThemeModeDialogTest.kt | 25 +++++++++++++------ 7 files changed, 49 insertions(+), 74 deletions(-) diff --git a/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothDeniedDialogTest.kt b/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothDeniedDialogTest.kt index da88064..9cd0a9f 100644 --- a/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothDeniedDialogTest.kt +++ b/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothDeniedDialogTest.kt @@ -18,28 +18,21 @@ class BluetoothDeniedDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルとメッセージが表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されてOpen Settingsを押す`() { + var confirmed = false composeTestRule.setContent { - BluetoothDeniedDialog(onDismiss = {}, onConfirm = {}) + BluetoothDeniedDialog(onDismiss = {}, onConfirm = { confirmed = true }) } composeTestRule.onNodeWithText("Bluetooth is Off").assertIsDisplayed() composeTestRule.onNodeWithText("Bluetooth was not enabled. Please enable it in Settings.").assertIsDisplayed() - } - - @Test - fun `Open SettingsボタンをタップするとonConfirmが呼ばれる`() { - var confirmed = false - composeTestRule.setContent { - BluetoothDeniedDialog(onDismiss = {}, onConfirm = { confirmed = true }) - } composeTestRule.onNodeWithText("Open Settings").performClick() assertTrue(confirmed) } @Test - fun `CancelボタンをタップするとonDismissが呼ばれる`() { + fun `ダイアログが表示されてキャンセルを押す`() { var dismissed = false composeTestRule.setContent { BluetoothDeniedDialog(onDismiss = { dismissed = true }, onConfirm = {}) diff --git a/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothUnavailableDialogTest.kt b/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothUnavailableDialogTest.kt index b264773..fb6db1a 100644 --- a/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothUnavailableDialogTest.kt +++ b/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/BluetoothUnavailableDialogTest.kt @@ -18,9 +18,10 @@ class BluetoothUnavailableDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルとメッセージが表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されてOKを押す`() { + var dismissed = false composeTestRule.setContent { - BluetoothUnavailableDialog(onDismiss = {}) + BluetoothUnavailableDialog(onDismiss = { dismissed = true }) } composeTestRule.onNodeWithText("Bluetooth Unavailable").assertIsDisplayed() @@ -28,14 +29,6 @@ class BluetoothUnavailableDialogTest { .onNodeWithText( "This device does not have Bluetooth, so this app cannot be used.", ).assertIsDisplayed() - } - - @Test - fun `OKボタンをタップするとonDismissが呼ばれる`() { - var dismissed = false - composeTestRule.setContent { - BluetoothUnavailableDialog(onDismiss = { dismissed = true }) - } composeTestRule.onNodeWithText("OK").performClick() assertTrue(dismissed) diff --git a/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/PermissionDeniedDialogTest.kt b/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/PermissionDeniedDialogTest.kt index 9a76ff2..3e21baf 100644 --- a/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/PermissionDeniedDialogTest.kt +++ b/feature/onboarding/src/test/java/kurou/androidpods/feature/onboarding/PermissionDeniedDialogTest.kt @@ -18,9 +18,10 @@ class PermissionDeniedDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルとメッセージが表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されてOpen Settingsを押す`() { + var confirmed = false composeTestRule.setContent { - PermissionDeniedDialog(onDismiss = {}, onConfirm = {}) + PermissionDeniedDialog(onDismiss = {}, onConfirm = { confirmed = true }) } composeTestRule.onNodeWithText("Permission Required").assertIsDisplayed() @@ -28,21 +29,13 @@ class PermissionDeniedDialogTest { .onNodeWithText( "Bluetooth permissions were denied. Please grant them in Settings.", ).assertIsDisplayed() - } - - @Test - fun `Open SettingsボタンをタップするとonConfirmが呼ばれる`() { - var confirmed = false - composeTestRule.setContent { - PermissionDeniedDialog(onDismiss = {}, onConfirm = { confirmed = true }) - } composeTestRule.onNodeWithText("Open Settings").performClick() assertTrue(confirmed) } @Test - fun `CancelボタンをタップするとonDismissが呼ばれる`() { + fun `ダイアログが表示されてキャンセルを押す`() { var dismissed = false composeTestRule.setContent { PermissionDeniedDialog(onDismiss = { dismissed = true }, onConfirm = {}) diff --git a/feature/settings/src/test/java/kurou/androidpods/feature/settings/OverlayPositionDialogTest.kt b/feature/settings/src/test/java/kurou/androidpods/feature/settings/OverlayPositionDialogTest.kt index 34fa17f..afe5db9 100644 --- a/feature/settings/src/test/java/kurou/androidpods/feature/settings/OverlayPositionDialogTest.kt +++ b/feature/settings/src/test/java/kurou/androidpods/feature/settings/OverlayPositionDialogTest.kt @@ -20,37 +20,26 @@ class OverlayPositionDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルと選択肢が表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されていずれかを選択する`() { + var selected: OverlayPosition? = null composeTestRule.setContent { OverlayPositionDialog( currentPosition = OverlayPosition.TOP, onDismiss = {}, - onPositionSelected = {}, + onPositionSelected = { selected = it }, ) } composeTestRule.onNodeWithText("Overlay position").assertIsDisplayed() composeTestRule.onNodeWithText("Top").assertIsDisplayed() composeTestRule.onNodeWithText("Bottom").assertIsDisplayed() - } - - @Test - fun `選択肢をタップするとonPositionSelectedが呼ばれる`() { - var selected: OverlayPosition? = null - composeTestRule.setContent { - OverlayPositionDialog( - currentPosition = OverlayPosition.TOP, - onDismiss = {}, - onPositionSelected = { selected = it }, - ) - } composeTestRule.onNodeWithText("Bottom").performClick() assertEquals(OverlayPosition.BOTTOM, selected) } @Test - fun `CancelボタンをタップするとonDismissが呼ばれる`() { + fun `ダイアログが表示されてキャンセルを押す`() { var dismissed = false composeTestRule.setContent { OverlayPositionDialog( diff --git a/feature/settings/src/test/java/kurou/androidpods/feature/settings/PermissionRequiredDialogTest.kt b/feature/settings/src/test/java/kurou/androidpods/feature/settings/PermissionRequiredDialogTest.kt index 3bb920e..9da75db 100644 --- a/feature/settings/src/test/java/kurou/androidpods/feature/settings/PermissionRequiredDialogTest.kt +++ b/feature/settings/src/test/java/kurou/androidpods/feature/settings/PermissionRequiredDialogTest.kt @@ -18,9 +18,10 @@ class PermissionRequiredDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルとメッセージが表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されてOpen Settingsを押す`() { + var confirmed = false composeTestRule.setContent { - PermissionRequiredDialog(onDismiss = {}, onConfirm = {}) + PermissionRequiredDialog(onDismiss = {}, onConfirm = { confirmed = true }) } composeTestRule.onNodeWithText("Bluetooth Permission Required").assertIsDisplayed() @@ -28,21 +29,13 @@ class PermissionRequiredDialogTest { .onNodeWithText( "This app requires Bluetooth permissions to connect to devices. Please grant permissions in Settings.", ).assertIsDisplayed() - } - - @Test - fun `Open SettingsボタンをタップするとonConfirmが呼ばれる`() { - var confirmed = false - composeTestRule.setContent { - PermissionRequiredDialog(onDismiss = {}, onConfirm = { confirmed = true }) - } composeTestRule.onNodeWithText("Open Settings").performClick() assertTrue(confirmed) } @Test - fun `CancelボタンをタップするとonDismissが呼ばれる`() { + fun `ダイアログが表示されてキャンセルを押す`() { var dismissed = false composeTestRule.setContent { PermissionRequiredDialog(onDismiss = { dismissed = true }, onConfirm = {}) diff --git a/feature/settings/src/test/java/kurou/androidpods/feature/settings/RssiThresholdDialogTest.kt b/feature/settings/src/test/java/kurou/androidpods/feature/settings/RssiThresholdDialogTest.kt index 18d4f72..2881f86 100644 --- a/feature/settings/src/test/java/kurou/androidpods/feature/settings/RssiThresholdDialogTest.kt +++ b/feature/settings/src/test/java/kurou/androidpods/feature/settings/RssiThresholdDialogTest.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import kurou.androidpods.core.domain.RssiThreshold import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -19,12 +20,13 @@ class RssiThresholdDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルと選択肢が表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されていずれかを選択する`() { + var selected: RssiThreshold? = null composeTestRule.setContent { RssiThresholdDialog( currentThreshold = RssiThreshold.VERY_NEAR, onDismiss = {}, - onThresholdSelected = {}, + onThresholdSelected = { selected = it }, ) } @@ -33,20 +35,23 @@ class RssiThresholdDialogTest { composeTestRule.onNodeWithText("Medium range (~5m)").assertIsDisplayed() composeTestRule.onNodeWithText("Near range (~2m)").assertIsDisplayed() composeTestRule.onNodeWithText("Very near (~1m)").assertIsDisplayed() + + composeTestRule.onNodeWithText("All devices").performClick() + assertEquals(RssiThreshold.ALL, selected) } @Test - fun `選択肢をタップするとonThresholdSelectedが呼ばれる`() { - var selected: RssiThreshold? = null + fun `ダイアログが表示されてキャンセルを押す`() { + var dismissed = false composeTestRule.setContent { RssiThresholdDialog( currentThreshold = RssiThreshold.VERY_NEAR, - onDismiss = {}, - onThresholdSelected = { selected = it }, + onDismiss = { dismissed = true }, + onThresholdSelected = {}, ) } - composeTestRule.onNodeWithText("All devices").performClick() - assertEquals(RssiThreshold.ALL, selected) + composeTestRule.onNodeWithText("Cancel").performClick() + assertTrue(dismissed) } } diff --git a/feature/settings/src/test/java/kurou/androidpods/feature/settings/ThemeModeDialogTest.kt b/feature/settings/src/test/java/kurou/androidpods/feature/settings/ThemeModeDialogTest.kt index c8e061a..84db810 100644 --- a/feature/settings/src/test/java/kurou/androidpods/feature/settings/ThemeModeDialogTest.kt +++ b/feature/settings/src/test/java/kurou/androidpods/feature/settings/ThemeModeDialogTest.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import kurou.androidpods.core.domain.ThemeMode import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -19,29 +20,37 @@ class ThemeModeDialogTest { val composeTestRule = createComposeRule() @Test - fun `タイトルと選択肢が表示される`() { + fun `ダイアログが表示されて必要な文字列が全て表示されていずれかを選択する`() { + var selected: ThemeMode? = null composeTestRule.setContent { - ThemeModeDialog(currentMode = ThemeMode.SYSTEM, onDismiss = {}, onModeSelected = {}) + ThemeModeDialog( + currentMode = ThemeMode.SYSTEM, + onDismiss = {}, + onModeSelected = { selected = it }, + ) } composeTestRule.onNodeWithText("Theme").assertIsDisplayed() composeTestRule.onNodeWithText("System").assertIsDisplayed() composeTestRule.onNodeWithText("Light").assertIsDisplayed() composeTestRule.onNodeWithText("Dark").assertIsDisplayed() + + composeTestRule.onNodeWithText("Dark").performClick() + assertEquals(ThemeMode.DARK, selected) } @Test - fun `選択肢をタップするとonModeSelectedが呼ばれる`() { - var selected: ThemeMode? = null + fun `ダイアログが表示されてキャンセルを押す`() { + var dismissed = false composeTestRule.setContent { ThemeModeDialog( currentMode = ThemeMode.SYSTEM, - onDismiss = {}, - onModeSelected = { selected = it }, + onDismiss = { dismissed = true }, + onModeSelected = {}, ) } - composeTestRule.onNodeWithText("Dark").performClick() - assertEquals(ThemeMode.DARK, selected) + composeTestRule.onNodeWithText("Cancel").performClick() + assertTrue(dismissed) } } From efbff08a5b7716a00cd4ebeb3e52ed791d5b3f89 Mon Sep 17 00:00:00 2001 From: ai-kurou Date: Fri, 29 May 2026 17:47:20 +0900 Subject: [PATCH 3/3] =?UTF-8?q?ci:=20Qodana=E3=82=B8=E3=83=A7=E3=83=96?= =?UTF-8?q?=E3=82=92CI=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=81=AB=E7=B5=B1=E5=90=88=E3=81=97=E3=81=A6qodana=5F?= =?UTF-8?q?code=5Fquality.yml=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/on-main-merge.yml | 24 +++++++++++++++ .github/workflows/on-pull-request.yml | 25 ++++++++++++++++ .github/workflows/qodana_code_quality.yml | 36 ----------------------- 3 files changed, 49 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/qodana_code_quality.yml diff --git a/.github/workflows/on-main-merge.yml b/.github/workflows/on-main-merge.yml index 0a90ea6..c7dcda2 100644 --- a/.github/workflows/on-main-merge.yml +++ b/.github/workflows/on-main-merge.yml @@ -138,3 +138,27 @@ jobs: api-level: 36 arch: x86_64 script: ./gradlew connectedDebugAndroidTest + + qodana: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + checks: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # a full history is required for analysis + + - name: 'Qodana Scan' + # NOTE: continue-on-error: true はすべてのエラー(認証失敗・ネットワーク障害・コード品質違反を含む)を + # 無音で飲み込むため、このジョブはCIゲートとして機能しない。 + # AGP 9.2.1 が Qodana でサポートされたら continue-on-error を削除すること。 + # 対応状況は JetBrains YouTrack で "Qodana AGP 9.2" を検索して確認する。 + continue-on-error: true + uses: JetBrains/qodana-action@d7b5ec2fbec32197ef447c450e00589ed5f34fd5 # v2026.1 + with: + pr-mode: false + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_693445069 }} diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 2b64d16..8de0048 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -127,3 +127,28 @@ jobs: api-level: 36 arch: x86_64 script: ./gradlew connectedDebugAndroidTest + + qodana: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + checks: write + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + + - name: 'Qodana Scan' + # NOTE: continue-on-error: true はすべてのエラー(認証失敗・ネットワーク障害・コード品質違反を含む)を + # 無音で飲み込むため、このジョブはCIゲートとして機能しない。 + # AGP 9.2.1 が Qodana でサポートされたら continue-on-error を削除すること。 + # 対応状況は JetBrains YouTrack で "Qodana AGP 9.2" を検索して確認する。 + continue-on-error: true + uses: JetBrains/qodana-action@d7b5ec2fbec32197ef447c450e00589ed5f34fd5 # v2026.1 + with: + pr-mode: true + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_693445069 }} diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 1d1782c..0000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Qodana は on-pull-request.yml / on-main-merge.yml とは別ファイルで管理する。 -# 理由: Qodana は Docker コンテナ (jetbrains/qodana-jvm-android) を pull して -# JetBrains IDE エンジンを丸ごと起動するため、Gradle タスクを直接実行する -# 他のジョブとは性質が異なる(実行時間・専用シークレット・Cloud へのアップロードなど)。 -# 別ファイルにするのが JetBrains 公式の推奨パターン。 -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit - fetch-depth: 0 # a full history is required for pull request analysis - - name: 'Qodana Scan' - # NOTE: continue-on-error: true はすべてのエラー(認証失敗・ネットワーク障害・コード品質違反を含む)を - # 無音で飲み込むため、このジョブはCIゲートとして機能しない。 - # AGP 9.2.1 が Qodana でサポートされたら continue-on-error を削除すること。 - # 対応状況は JetBrains YouTrack で "Qodana AGP 9.2" を検索して確認する。 - continue-on-error: true - uses: JetBrains/qodana-action@d7b5ec2fbec32197ef447c450e00589ed5f34fd5 # v2026.1 - with: - pr-mode: true - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_693445069 }}