@@ -1291,108 +1291,171 @@ func TestInsightsRows_WithItems(t *testing.T) {
12911291 assert .True (t , rows [1 ].Target .InfoOnly )
12921292}
12931293
1294- func TestInsightsSection_AppearsInDashboardView (t * testing.T ) {
1294+ func TestInsightsSection_AppearsAfterResultMsg (t * testing.T ) {
12951295 t .Parallel ()
12961296 m := newTestModel (t )
12971297 m .insightsEnabled = true
1298- m .showDashboard = true
1299- m .dash .data = nonEmptyDashboard ()
1300- m .dash .insights .items = []insightItem {
1301- {Text : "Test insight" , Tab : "appliances" , EntityID : 1 },
1298+ m .width = 120
1299+ m .height = 40
1300+
1301+ // Open dashboard with D.
1302+ sendKey (m , "D" )
1303+ require .True (t , m .showDashboard )
1304+
1305+ // Simulate LLM response arriving via insightsResultMsg.
1306+ m .Update (insightsResultMsg {
1307+ Items : []insightItem {
1308+ {Text : "Water heater is 12y old" , Tab : "appliances" , EntityID : 1 },
1309+ },
1310+ })
1311+ m .prepareDashboardView ()
1312+
1313+ // Expand insights section (last section).
1314+ sendKey (m , "G" ) // jump to bottom
1315+ // Find and expand the insights header.
1316+ for m .dash .cursor > 0 {
1317+ entry := m .dash .nav [m .dash .cursor ]
1318+ if entry .IsHeader && entry .Section == dashSectionInsights {
1319+ break
1320+ }
1321+ sendKey (m , "k" )
13021322 }
1303- m .dash .insights .generatedAt = time .Now ()
1304- m .dash .expanded = map [string ]bool {dashSectionInsights : true }
1305- m .buildDashNav ()
1323+ sendKey (m , "e" ) // expand
1324+ m .prepareDashboardView ()
13061325
1307- view := m .dashboardView ( 50 , 80 )
1308- assert .Contains (t , view , dashSectionInsights )
1309- assert .Contains (t , view , "Test insight " )
1326+ overlay := m .buildDashboardOverlay ( )
1327+ assert .Contains (t , overlay , dashSectionInsights )
1328+ assert .Contains (t , overlay , "Water heater is 12y old " )
13101329}
13111330
1312- func TestInsightsSection_LoadingState (t * testing.T ) {
1331+ func TestInsightsSection_LoadingShowsSpinner (t * testing.T ) {
13131332 t .Parallel ()
13141333 m := newTestModel (t )
13151334 m .insightsEnabled = true
1316- // Need llmClient to be non-nil for insightsWanted.
1317- // We can just set the flag directly for rendering tests.
1318- m .showDashboard = true
1319- m .dash .data = nonEmptyDashboard ()
1335+ m .width = 120
1336+ m .height = 40
1337+
1338+ // Open dashboard.
1339+ sendKey (m , "D" )
1340+ require .True (t , m .showDashboard )
1341+
1342+ // Simulate loading state (insights fetch in progress).
13201343 m .dash .insights .loading = true
1321- m .buildDashNav ()
1344+ m .prepareDashboardView ()
13221345
1323- view := m .dashboardView ( 50 , 80 )
1324- assert .Contains (t , view , "analyzing..." )
1346+ overlay := m .buildDashboardOverlay ( )
1347+ assert .Contains (t , overlay , "analyzing..." )
13251348}
13261349
1327- func TestInsightsSection_ErrorState (t * testing.T ) {
1350+ func TestInsightsSection_ErrorShowsMutedMessage (t * testing.T ) {
13281351 t .Parallel ()
13291352 m := newTestModel (t )
13301353 m .insightsEnabled = true
1331- m .showDashboard = true
1332- m .dash .data = nonEmptyDashboard ()
1333- m .dash .insights .err = fmt .Errorf ("network timeout" )
1334- m .buildDashNav ()
1354+ m .width = 120
1355+ m .height = 40
1356+
1357+ // Open dashboard.
1358+ sendKey (m , "D" )
1359+ require .True (t , m .showDashboard )
1360+
1361+ // Simulate error result arriving.
1362+ m .Update (insightsResultMsg {Err : fmt .Errorf ("network timeout" )})
1363+ m .prepareDashboardView ()
13351364
1336- view := m .dashboardView ( 50 , 80 )
1337- assert .Contains (t , view , "unavailable" )
1338- assert .Contains (t , view , "network timeout" )
1339- assert .NotContains (t , view , "error:" )
1365+ overlay := m .buildDashboardOverlay ( )
1366+ assert .Contains (t , overlay , "unavailable" )
1367+ assert .Contains (t , overlay , "network timeout" )
1368+ assert .NotContains (t , overlay , "error:" )
13401369}
13411370
1342- func TestInsightsNav_JumpsToEntity (t * testing.T ) {
1371+ func TestInsightsNav_JumpsToEntityViaKeyboard (t * testing.T ) {
13431372 t .Parallel ()
13441373 m := newTestModel (t )
1345- m .showDashboard = true
1346- m .dash .data = nonEmptyDashboard ()
1347- m .dash .insights .items = []insightItem {
1348- {Text : "Test insight" , Tab : "appliances" , EntityID : 42 },
1349- }
1350- m .dash .expanded = map [string ]bool {dashSectionInsights : true }
1351- m .buildDashNav ()
1374+ m .insightsEnabled = true
1375+ m .width = 120
1376+ m .height = 40
13521377
1353- // Find the insights data entry in nav.
1354- found := false
1355- for i , entry := range m .dash .nav {
1356- if entry .Section == dashSectionInsights && ! entry .IsHeader {
1357- found = true
1358- m .dash .cursor = i
1378+ // Open dashboard.
1379+ sendKey (m , "D" )
1380+ require .True (t , m .showDashboard )
1381+
1382+ // Deliver insights.
1383+ m .Update (insightsResultMsg {
1384+ Items : []insightItem {
1385+ {Text : "Test insight" , Tab : "appliances" , EntityID : 42 },
1386+ },
1387+ })
1388+ m .prepareDashboardView ()
1389+
1390+ // Navigate to the Insights section header with J (jump between sections).
1391+ for range 20 {
1392+ entry := m .dash .nav [m .dash .cursor ]
1393+ if entry .IsHeader && entry .Section == dashSectionInsights {
13591394 break
13601395 }
1396+ sendKey (m , "J" )
13611397 }
1362- require .True (t , found , "should have an insights nav entry" )
1398+ require .Equal (t , dashSectionInsights , m .dash .nav [m .dash .cursor ].Section )
1399+
1400+ // Expand it.
1401+ sendKey (m , "e" )
1402+ m .prepareDashboardView ()
13631403
1364- // Press enter to jump.
1404+ // Move down to the data row.
1405+ sendKey (m , "j" )
1406+ require .False (t , m .dash .nav [m .dash .cursor ].IsHeader , "should be on data row" )
1407+
1408+ // Press enter to jump to the entity.
13651409 sendKey (m , "enter" )
1366- assert .False (t , m .showDashboard , "enter should close dashboard " )
1410+ assert .False (t , m .showDashboard , "dashboard should close" )
13671411 assert .Equal (t , tabIndex (tabAppliances ), m .active )
13681412}
13691413
1370- func TestInsightsRefreshKey (t * testing.T ) {
1414+ func TestInsightsRefreshKey_MarksStale (t * testing.T ) {
13711415 t .Parallel ()
13721416 m := newTestModel (t )
1373- m .showDashboard = true
13741417 m .insightsEnabled = true
1375- m .dash .data = nonEmptyDashboard ()
1376- m .dash .insights .items = []insightItem {
1377- {Text : "Old insight" , Tab : "appliances" , EntityID : 1 },
1378- }
1379- m .dash .insights .stale = false
1380- m .buildDashNav ()
1418+ m .width = 120
1419+ m .height = 40
1420+
1421+ // Open dashboard.
1422+ sendKey (m , "D" )
1423+ require .True (t , m .showDashboard )
1424+
1425+ // Deliver insights so they're cached.
1426+ m .Update (insightsResultMsg {
1427+ Items : []insightItem {
1428+ {Text : "Old insight" , Tab : "appliances" , EntityID : 1 },
1429+ },
1430+ })
1431+ m .prepareDashboardView ()
1432+ require .False (t , m .dash .insights .stale )
13811433
1382- // r key should mark insights stale (even without llmClient) .
1434+ // Press r to refresh .
13831435 sendKey (m , "r" )
13841436 assert .True (t , m .dash .insights .stale )
13851437}
13861438
1387- func TestMarkInsightsStale_OnMutation (t * testing.T ) {
1439+ func TestInsightsStale_AfterMutation (t * testing.T ) {
13881440 t .Parallel ()
13891441 m := newTestModelWithStore (t )
13901442 m .insightsEnabled = true
1391- m .dash .insights .items = []insightItem {
1392- {Text : "Old insight" , Tab : "appliances" , EntityID : 1 },
1393- }
1394- m .dash .insights .stale = false
1443+ m .width = 120
1444+ m .height = 40
1445+
1446+ // Open dashboard.
1447+ sendKey (m , "D" )
1448+ require .True (t , m .showDashboard )
1449+
1450+ // Deliver insights.
1451+ m .Update (insightsResultMsg {
1452+ Items : []insightItem {
1453+ {Text : "Old insight" , Tab : "appliances" , EntityID : 1 },
1454+ },
1455+ })
1456+ require .False (t , m .dash .insights .stale )
13951457
1458+ // Simulate a data mutation (e.g. user saved an edit).
13961459 m .reloadAfterMutation ()
13971460 assert .True (t , m .dash .insights .stale , "mutation should mark insights stale" )
13981461}
0 commit comments