|
| 1 | +package processing.app.ui |
| 2 | + |
| 3 | +import androidx.compose.ui.test.* |
| 4 | +import org.junit.jupiter.api.Test |
| 5 | +import org.mockito.kotlin.mock |
| 6 | +import org.mockito.kotlin.verify |
| 7 | +import org.mockito.kotlin.verifyNoInteractions |
| 8 | +import processing.app.Base |
| 9 | +import processing.app.ui.theme.PDETheme |
| 10 | +import processing.app.api.Sketch.Companion.Sketch |
| 11 | + |
| 12 | +@OptIn(ExperimentalTestApi::class) |
| 13 | +class PDEWelcomeTest { |
| 14 | + |
| 15 | + // Critical Function Tests |
| 16 | + |
| 17 | + @Test |
| 18 | + fun testWelcomeScreenRendersWithoutBase() = runComposeUiTest { |
| 19 | + setContent { |
| 20 | + PDETheme { PDEWelcome(base = null) } |
| 21 | + } |
| 22 | + waitForIdle() |
| 23 | + } |
| 24 | + |
| 25 | + @Test |
| 26 | + fun testWelcomeScreenRendersWithBase() = runComposeUiTest { |
| 27 | + val base: Base = mock() |
| 28 | + setContent { |
| 29 | + PDETheme { PDEWelcome(base = base) } |
| 30 | + } |
| 31 | + waitForIdle() |
| 32 | + } |
| 33 | + |
| 34 | + |
| 35 | + // Action button visibility |
| 36 | + |
| 37 | + @Test |
| 38 | + fun testNewSketchButtonIsDisplayed() = runComposeUiTest { |
| 39 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 40 | + onNodeWithText(Labels.NEW_SKETCH, substring = true).assertIsDisplayed() |
| 41 | + } |
| 42 | + |
| 43 | + @Test |
| 44 | + fun testSketchbookButtonIsDisplayed() = runComposeUiTest { |
| 45 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 46 | + onNodeWithText(Labels.SKETCHBOOK, substring = true).assertIsDisplayed() |
| 47 | + } |
| 48 | + |
| 49 | + @Test |
| 50 | + fun testExamplesButtonIsDisplayed() = runComposeUiTest { |
| 51 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 52 | + onNodeWithText(Labels.EXAMPLES, substring = true).assertIsDisplayed() |
| 53 | + } |
| 54 | + |
| 55 | + // Action button clicks |
| 56 | + |
| 57 | + @Test |
| 58 | + fun testNewSketchButtonCallsHandleNew() = runComposeUiTest { |
| 59 | + val base: Base = mock() |
| 60 | + setContent { PDETheme { PDEWelcome(base = base) } } |
| 61 | + onNodeWithText(Labels.NEW_SKETCH, substring = true).performClick() |
| 62 | + verify(base).handleNew() |
| 63 | + } |
| 64 | + |
| 65 | + @Test |
| 66 | + fun testSketchbookButtonCallsShowSketchbookFrame() = runComposeUiTest { |
| 67 | + val base: Base = mock() |
| 68 | + setContent { PDETheme { PDEWelcome(base = base) } } |
| 69 | + onNodeWithText(Labels.SKETCHBOOK, substring = true).performClick() |
| 70 | + verify(base).showSketchbookFrame() |
| 71 | + } |
| 72 | + |
| 73 | + @Test |
| 74 | + fun debugSemanticTree() = runComposeUiTest { |
| 75 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 76 | + waitForIdle() |
| 77 | + onRoot().printToLog("PDEWelcome") |
| 78 | + } |
| 79 | + |
| 80 | + @Test |
| 81 | + fun testExamplesButtonCallsShowExamplesFrame() = runComposeUiTest { |
| 82 | + val base: Base = mock() |
| 83 | + setContent { PDETheme { PDEWelcome(base = base) } } |
| 84 | + onNodeWithText(Labels.EXAMPLES, substring = true).performClick() |
| 85 | + verify(base).showExamplesFrame() |
| 86 | + } |
| 87 | + |
| 88 | + // Null-base safety |
| 89 | + |
| 90 | + @Test |
| 91 | + fun testNewSketchWithNullBaseDoesNotCrash() = runComposeUiTest { |
| 92 | + setContent { PDETheme { PDEWelcome(base = null) } } |
| 93 | + onNodeWithText(Labels.NEW_SKETCH, substring = true).performClick() |
| 94 | + waitForIdle() |
| 95 | + } |
| 96 | + |
| 97 | + @Test |
| 98 | + fun testSketchbookWithNullBaseDoesNotCrash() = runComposeUiTest { |
| 99 | + setContent { PDETheme { PDEWelcome(base = null) } } |
| 100 | + onNodeWithText(Labels.SKETCHBOOK, substring = true).performClick() |
| 101 | + waitForIdle() |
| 102 | + } |
| 103 | + |
| 104 | + @Test |
| 105 | + fun testExamplesWithNullBaseDoesNotCrash() = runComposeUiTest { |
| 106 | + setContent { PDETheme { PDEWelcome(base = null) } } |
| 107 | + onNodeWithText(Labels.EXAMPLES, substring = true).performClick() |
| 108 | + waitForIdle() |
| 109 | + } |
| 110 | + |
| 111 | + @Test |
| 112 | + fun testNoBaseMethodsCalledWhenBaseIsNull() = runComposeUiTest { |
| 113 | + val base: Base = mock() |
| 114 | + setContent { PDETheme { PDEWelcome(base = null) } } |
| 115 | + onNodeWithText(Labels.NEW_SKETCH, substring = true).performClick() |
| 116 | + onNodeWithText(Labels.SKETCHBOOK, substring = true).performClick() |
| 117 | + onNodeWithText(Labels.EXAMPLES, substring = true).performClick() |
| 118 | + verifyNoInteractions(base) |
| 119 | + } |
| 120 | + |
| 121 | + // Show on startup checkbox |
| 122 | + |
| 123 | + @Test |
| 124 | + fun testShowOnStartupCheckboxIsDisplayed() = runComposeUiTest { |
| 125 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 126 | + onNodeWithText(Labels.SHOW_ON_STARTUP, substring = true).assertIsDisplayed() |
| 127 | + } |
| 128 | + |
| 129 | + @Test |
| 130 | + fun testShowOnStartupCheckboxTogglesPreference() = runComposeUiTest { |
| 131 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 132 | + onNodeWithText(Labels.SHOW_ON_STARTUP, substring = true).performClick() |
| 133 | + waitForIdle() |
| 134 | + // Row must still be present after toggling |
| 135 | + onNodeWithText(Labels.SHOW_ON_STARTUP, substring = true).assertIsDisplayed() |
| 136 | + } |
| 137 | + |
| 138 | + // Resource & community links |
| 139 | + |
| 140 | + @Test |
| 141 | + fun testGetStartedLinkIsDisplayed() = runComposeUiTest { |
| 142 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 143 | + onNodeWithText(Labels.GET_STARTED, substring = true).assertIsDisplayed() |
| 144 | + } |
| 145 | + |
| 146 | + @Test |
| 147 | + fun testTutorialsLinkIsDisplayed() = runComposeUiTest { |
| 148 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 149 | + onNodeWithText(Labels.TUTORIALS, substring = true).assertIsDisplayed() |
| 150 | + } |
| 151 | + |
| 152 | + @Test |
| 153 | + fun testDocumentationLinkIsDisplayed() = runComposeUiTest { |
| 154 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 155 | + onNodeWithText(Labels.DOCUMENTATION, substring = true).assertIsDisplayed() |
| 156 | + } |
| 157 | + |
| 158 | + @Test |
| 159 | + fun testForumLinkIsDisplayed() = runComposeUiTest { |
| 160 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 161 | + onNodeWithText(Labels.FORUM, substring = true).assertIsDisplayed() |
| 162 | + } |
| 163 | + |
| 164 | + @Test |
| 165 | + fun testDiscordLinkIsDisplayed() = runComposeUiTest { |
| 166 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 167 | + onNodeWithText("Discord", substring = true).assertIsDisplayed() |
| 168 | + } |
| 169 | + |
| 170 | + @Test |
| 171 | + fun testGithubLinkIsDisplayed() = runComposeUiTest { |
| 172 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 173 | + onNodeWithText("GitHub", substring = true).assertIsDisplayed() |
| 174 | + } |
| 175 | + |
| 176 | + @Test |
| 177 | + fun testInstagramLinkIsDisplayed() = runComposeUiTest { |
| 178 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 179 | + onNodeWithText("Instagram", substring = true).assertIsDisplayed() |
| 180 | + } |
| 181 | + |
| 182 | + // Examples list |
| 183 | + |
| 184 | + @Test |
| 185 | + fun testExamplesListIsDisplayed() = runComposeUiTest { |
| 186 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 187 | + waitForIdle() |
| 188 | + onAllNodesWithText(Labels.OPEN_SKETCH, substring = true) |
| 189 | + .onFirst() |
| 190 | + .assertExists() |
| 191 | + } |
| 192 | + |
| 193 | + @Test |
| 194 | + fun testExamplesListFallsBackToDefaultsWhenNoSketches() = runComposeUiTest { |
| 195 | + // When listAllExamples() yields nothing, PDEWelcome falls back to the |
| 196 | + // 4 hard-coded sketches. Either way at least one card must exist. |
| 197 | + setContent { PDETheme { PDEWelcome(base = mock()) } } |
| 198 | + waitForIdle() |
| 199 | + onAllNodesWithText(Labels.OPEN_SKETCH, substring = true) |
| 200 | + .onFirst() |
| 201 | + .assertExists() |
| 202 | + } |
| 203 | + |
| 204 | + @Test |
| 205 | + fun testSketchCardOpenButtonTriggersCallback() = runComposeUiTest { |
| 206 | + var opened = false |
| 207 | + setContent { |
| 208 | + PDETheme { |
| 209 | + val sketch = Sketch(path = "/tmp", name = "test") |
| 210 | + sketch.card(onOpen = { opened = true }) |
| 211 | + } |
| 212 | + } |
| 213 | + // Hover to reveal the overlay |
| 214 | + onRoot().performMouseInput { moveTo(center) } |
| 215 | + waitForIdle() |
| 216 | + onNodeWithText(Labels.OPEN_SKETCH, substring = true).performClick() |
| 217 | + assert(opened) |
| 218 | + } |
| 219 | + |
| 220 | + @Test |
| 221 | + fun testSketchCardHoverRevealsBanner() = runComposeUiTest { |
| 222 | + setContent { |
| 223 | + PDETheme { |
| 224 | + val sketch = Sketch(path = "/tmp", name = "MySketch") |
| 225 | + sketch.card() |
| 226 | + } |
| 227 | + } |
| 228 | + onRoot().performMouseInput { moveTo(center) } |
| 229 | + waitForIdle() |
| 230 | + onNodeWithText("MySketch", substring = true).assertIsDisplayed() |
| 231 | + } |
| 232 | + |
| 233 | + @Test |
| 234 | + fun testPDEWelcomeWithSurveyRendersWithoutCrash() = runComposeUiTest { |
| 235 | + setContent { PDETheme { PDEWelcomeWithSurvey(base = mock()) } } |
| 236 | + waitForIdle() |
| 237 | + } |
| 238 | + |
| 239 | + @Test |
| 240 | + fun testPDEWelcomeWithSurveyRendersWithNullBase() = runComposeUiTest { |
| 241 | + setContent { PDETheme { PDEWelcomeWithSurvey(base = null) } } |
| 242 | + waitForIdle() |
| 243 | + } |
| 244 | + |
| 245 | + // Label constants. Update if anything is changed |
| 246 | + |
| 247 | + private object Labels { |
| 248 | + const val NEW_SKETCH = "New Sketch" |
| 249 | + const val SKETCHBOOK = "My Sketches" // was "Sketchbook" |
| 250 | + const val EXAMPLES = "Open Examples" // was "Examples" |
| 251 | + const val SHOW_ON_STARTUP = "Show this window at startup" // was "Show on startup" |
| 252 | + const val GET_STARTED = "Get Started" |
| 253 | + const val TUTORIALS = "Tutorials" |
| 254 | + const val DOCUMENTATION = "Reference" // was "Documentation" |
| 255 | + const val FORUM = "Forum" |
| 256 | + const val OPEN_SKETCH = "Open" |
| 257 | + } |
| 258 | +} |
0 commit comments