Repository navigation
test(foundation): coverage matrix + strategy + gap-fill batch (#773) #980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6cabc89
f4e6fa8
6bca2c8
820ba24
2b1c22f
046a311
591dd30
11fc7ef
473c534
c067695
110598b
3755ccd
89e01ac
c00e45d
edf76b2
72667b5
8b79cc9
ca51764
b662ccc
c6a76d4
29da38c
6f85b2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import type { ActionableItem } from '../../../types/intelligence'; | ||
| import { filterItems, getItemStats, groupItemsByTime } from '../utils'; | ||
|
|
||
| // Pin the wall clock so day-boundary buckets are stable across the day and on CI. | ||
| const FIXED_NOW = new Date('2026-04-27T12:00:00.000Z'); | ||
|
|
||
| beforeAll(() => { | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(FIXED_NOW); | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| vi.useRealTimers(); | ||
| }); | ||
|
|
||
| function makeItem( | ||
| partial: Partial<ActionableItem> & { id: string; createdAt: Date } | ||
| ): ActionableItem { | ||
| return { | ||
| title: partial.title ?? 'Untitled', | ||
| description: partial.description, | ||
| source: partial.source ?? 'system', | ||
| priority: partial.priority ?? 'normal', | ||
| status: partial.status ?? 'active', | ||
| updatedAt: partial.updatedAt ?? partial.createdAt, | ||
| actionable: partial.actionable ?? false, | ||
| sourceLabel: partial.sourceLabel, | ||
| expiresAt: partial.expiresAt, | ||
| ...partial, | ||
| } as ActionableItem; | ||
| } | ||
|
|
||
| const HOUR = 60 * 60 * 1000; | ||
| const DAY = 24 * HOUR; | ||
|
|
||
| function daysAgo(n: number): Date { | ||
| return new Date(Date.now() - n * DAY); | ||
| } | ||
|
|
||
| describe('intelligence/utils — actionable item helpers (11.1.2)', () => { | ||
| describe('groupItemsByTime', () => { | ||
| it('buckets items into Today / Yesterday / This Week / Older', () => { | ||
| const items = [ | ||
| makeItem({ id: 'today', createdAt: new Date() }), | ||
| makeItem({ id: 'yest', createdAt: daysAgo(1) }), | ||
| makeItem({ id: 'wk', createdAt: daysAgo(3) }), | ||
| makeItem({ id: 'old', createdAt: daysAgo(30) }), | ||
| ]; | ||
|
|
||
| const groups = groupItemsByTime(items); | ||
| const labels = groups.map(g => g.label); | ||
| expect(labels).toEqual(['Today', 'Yesterday', 'This Week', 'Older']); | ||
|
|
||
| const find = (label: string) => groups.find(g => g.label === label); | ||
| expect(find('Today')?.items.map(i => i.id)).toEqual(['today']); | ||
| expect(find('Yesterday')?.items.map(i => i.id)).toEqual(['yest']); | ||
| expect(find('This Week')?.items.map(i => i.id)).toEqual(['wk']); | ||
| expect(find('Older')?.items.map(i => i.id)).toEqual(['old']); | ||
| }); | ||
|
|
||
| it('omits empty buckets and orders within a bucket by priority then recency', () => { | ||
| const items = [ | ||
| makeItem({ id: 'a', createdAt: new Date(), priority: 'normal' }), | ||
| makeItem({ id: 'b', createdAt: new Date(Date.now() - 2 * HOUR), priority: 'critical' }), | ||
| makeItem({ id: 'c', createdAt: new Date(Date.now() - HOUR), priority: 'critical' }), | ||
| ]; | ||
|
|
||
| const groups = groupItemsByTime(items); | ||
| expect(groups).toHaveLength(1); | ||
| const today = groups[0]; | ||
| expect(today.label).toBe('Today'); | ||
| // Critical first; within critical, newer first; normal last. | ||
| expect(today.items.map(i => i.id)).toEqual(['c', 'b', 'a']); | ||
| expect(today.count).toBe(3); | ||
| }); | ||
|
|
||
| it('handles an empty input as an empty group list', () => { | ||
| expect(groupItemsByTime([])).toEqual([]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('filterItems', () => { | ||
| const items = [ | ||
| makeItem({ | ||
| id: '1', | ||
| createdAt: new Date(), | ||
| title: 'Reply to Alice', | ||
| source: 'email', | ||
| priority: 'critical', | ||
| status: 'active', | ||
| sourceLabel: 'Gmail', | ||
| }), | ||
| makeItem({ | ||
| id: '2', | ||
| createdAt: new Date(), | ||
| title: 'Standup', | ||
| source: 'calendar', | ||
| priority: 'normal', | ||
| status: 'active', | ||
| sourceLabel: 'Calendar', | ||
| }), | ||
| makeItem({ | ||
| id: '3', | ||
| createdAt: new Date(), | ||
| title: 'Reply to Bob', | ||
| source: 'email', | ||
| priority: 'important', | ||
| status: 'completed', | ||
| sourceLabel: 'Gmail', | ||
| description: 'Follow-up on the canary deployment', | ||
| }), | ||
| ]; | ||
|
|
||
| it('filters by source', () => { | ||
| const out = filterItems(items, { source: 'email' }); | ||
| expect(out.map(i => i.id)).toEqual(['1', '3']); | ||
| }); | ||
|
|
||
| it('filters by priority', () => { | ||
| const out = filterItems(items, { priority: 'critical' }); | ||
| expect(out.map(i => i.id)).toEqual(['1']); | ||
| }); | ||
|
|
||
| it('filters by status', () => { | ||
| const out = filterItems(items, { status: 'completed' }); | ||
| expect(out.map(i => i.id)).toEqual(['3']); | ||
| }); | ||
|
|
||
| it('filters by searchTerm across title, description, and sourceLabel', () => { | ||
| expect(filterItems(items, { searchTerm: 'reply' }).map(i => i.id)).toEqual(['1', '3']); | ||
| expect(filterItems(items, { searchTerm: 'canary' }).map(i => i.id)).toEqual(['3']); | ||
| expect(filterItems(items, { searchTerm: 'gmail' }).map(i => i.id)).toEqual(['1', '3']); | ||
| }); | ||
|
Comment on lines
+131
to
+135
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 122 expects If `sourceLabel` matching is intended, patch `filterItems`--- a/app/src/components/intelligence/utils.ts
+++ b/app/src/components/intelligence/utils.ts
@@
if (options.searchTerm) {
const term = options.searchTerm.toLowerCase();
filtered = filtered.filter(item =>
item.title.toLowerCase().includes(term) ||
- (item.description && item.description.toLowerCase().includes(term))
+ (item.description && item.description.toLowerCase().includes(term)) ||
+ (item.sourceLabel && item.sourceLabel.toLowerCase().includes(term))
);
}🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. False positive — filtered = filtered.filter(
item =>
item.title.toLowerCase().includes(term) ||
item.description?.toLowerCase().includes(term) ||
item.sourceLabel?.toLowerCase().includes(term)
);The Vitest case is correct as-is.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 333
item.sourceLabel?.toLowerCase().includes(term)My comment was a false positive. The test and the implementation are consistent. Apologies for the noise! ✏️ Learnings added
🧠 Learnings used |
||
|
|
||
| it('treats "all" as a no-op for source/priority/status', () => { | ||
| const out = filterItems(items, { source: 'all', priority: 'all', status: 'all' }); | ||
| expect(out.map(i => i.id)).toEqual(['1', '2', '3']); | ||
| }); | ||
|
|
||
| it('returns no items when searchTerm matches nothing (failure path)', () => { | ||
| const out = filterItems(items, { searchTerm: 'definitely-not-present' }); | ||
| expect(out).toEqual([]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getItemStats', () => { | ||
| it('counts totals, priorities, and sources, and flags new + expiringSoon', () => { | ||
| const items = [ | ||
| makeItem({ | ||
| id: 'fresh', | ||
| createdAt: new Date(Date.now() - 60 * 1000), // 1 minute ago | ||
| priority: 'critical', | ||
| source: 'email', | ||
| }), | ||
| makeItem({ | ||
| id: 'old', | ||
| createdAt: new Date(Date.now() - 10 * DAY), | ||
| priority: 'normal', | ||
| source: 'calendar', | ||
| }), | ||
| makeItem({ | ||
| id: 'expSoon', | ||
| createdAt: new Date(Date.now() - 2 * HOUR), | ||
| priority: 'important', | ||
| source: 'email', | ||
| expiresAt: new Date(Date.now() + 6 * HOUR), | ||
| }), | ||
| ]; | ||
|
|
||
| const stats = getItemStats(items); | ||
| expect(stats.total).toBe(3); | ||
| expect(stats.byPriority.critical).toBe(1); | ||
| expect(stats.byPriority.important).toBe(1); | ||
| expect(stats.byPriority.normal).toBe(1); | ||
| expect(stats.bySource.email).toBe(2); | ||
| expect(stats.bySource.calendar).toBe(1); | ||
| expect(stats.newItems).toBe(1); | ||
| expect(stats.expiringSoon).toBe(1); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import { renderHook } from '@testing-library/react'; | ||
| import { describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import { useCoreState } from '../../../providers/CoreStateProvider'; | ||
| import { useAutocompleteSkillStatus } from '../useAutocompleteSkillStatus'; | ||
|
|
||
| vi.mock('../../../providers/CoreStateProvider', () => ({ useCoreState: vi.fn() })); | ||
|
|
||
| type AutocompleteRuntime = { | ||
| platform_supported: boolean; | ||
| running: boolean; | ||
| enabled: boolean; | ||
| last_error?: string | null; | ||
| }; | ||
|
|
||
| function mockSnapshot(autocomplete: AutocompleteRuntime | null): void { | ||
| vi.mocked(useCoreState).mockReturnValue({ snapshot: { runtime: { autocomplete } } } as ReturnType< | ||
| typeof useCoreState | ||
| >); | ||
| } | ||
|
|
||
| describe('useAutocompleteSkillStatus (5.2 — autocomplete settings status)', () => { | ||
| it('returns offline + Enable CTA when no runtime status is available yet', () => { | ||
| mockSnapshot(null); | ||
| const { result } = renderHook(() => useAutocompleteSkillStatus()); | ||
| expect(result.current.connectionStatus).toBe('offline'); | ||
| expect(result.current.statusLabel).toBe('Offline'); | ||
| expect(result.current.ctaLabel).toBe('Enable'); | ||
| expect(result.current.platformUnsupported).toBe(false); | ||
| }); | ||
|
|
||
| it('returns Unsupported when the platform reports the runtime is unsupported', () => { | ||
| mockSnapshot({ platform_supported: false, running: false, enabled: false }); | ||
| const { result } = renderHook(() => useAutocompleteSkillStatus()); | ||
| expect(result.current.connectionStatus).toBe('offline'); | ||
| expect(result.current.statusLabel).toBe('Unsupported'); | ||
| expect(result.current.ctaLabel).toBe('Details'); | ||
| expect(result.current.platformUnsupported).toBe(true); | ||
| }); | ||
|
|
||
| it('returns Active + Manage CTA when the runtime is running (overrides stale errors)', () => { | ||
| mockSnapshot({ | ||
| platform_supported: true, | ||
| running: true, | ||
| enabled: true, | ||
| last_error: 'stale: should not surface', | ||
| }); | ||
| const { result } = renderHook(() => useAutocompleteSkillStatus()); | ||
| expect(result.current.connectionStatus).toBe('connected'); | ||
| expect(result.current.statusLabel).toBe('Active'); | ||
| expect(result.current.ctaLabel).toBe('Manage'); | ||
| }); | ||
|
|
||
| it('returns Error + Retry CTA when not running and an error is present', () => { | ||
| mockSnapshot({ | ||
| platform_supported: true, | ||
| running: false, | ||
| enabled: true, | ||
| last_error: 'permission denied', | ||
| }); | ||
| const { result } = renderHook(() => useAutocompleteSkillStatus()); | ||
| expect(result.current.connectionStatus).toBe('error'); | ||
| expect(result.current.statusLabel).toBe('Error'); | ||
| expect(result.current.ctaLabel).toBe('Retry'); | ||
| expect(result.current.ctaVariant).toBe('amber'); | ||
| }); | ||
|
|
||
| it('returns Enabled (disconnected) + Manage when enabled but not running and no error', () => { | ||
| mockSnapshot({ platform_supported: true, running: false, enabled: true, last_error: null }); | ||
| const { result } = renderHook(() => useAutocompleteSkillStatus()); | ||
| expect(result.current.connectionStatus).toBe('disconnected'); | ||
| expect(result.current.statusLabel).toBe('Enabled'); | ||
| expect(result.current.ctaLabel).toBe('Manage'); | ||
| }); | ||
|
|
||
| it('returns Disabled + Enable CTA when not enabled and not running', () => { | ||
| mockSnapshot({ platform_supported: true, running: false, enabled: false, last_error: null }); | ||
| const { result } = renderHook(() => useAutocompleteSkillStatus()); | ||
| expect(result.current.connectionStatus).toBe('offline'); | ||
| expect(result.current.statusLabel).toBe('Disabled'); | ||
| expect(result.current.ctaLabel).toBe('Enable'); | ||
| expect(result.current.ctaVariant).toBe('sage'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,5 +72,13 @@ export const store = configureStore({ | |
|
|
||
| export const persistor = persistStore(store); | ||
|
|
||
| // Expose the store on `window` so WDIO E2E specs can read Redux state directly | ||
| // to assert backing-state changes (see app/test/e2e/specs/*.spec.ts). The store | ||
| // holds no secrets that aren't already in the renderer's memory; this only | ||
| // surfaces the existing handle under a stable, namespaced key. | ||
| if (typeof window !== 'undefined') { | ||
| (window as unknown as { __OPENHUMAN_STORE__?: typeof store }).__OPENHUMAN_STORE__ = store; | ||
| } | ||
|
Comment on lines
+75
to
+81
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restrict global store exposure to test/dev builds. Line 79 currently exposes Suggested hardening-// Expose the store on `window` so WDIO E2E specs can read Redux state directly
+// Expose the store on `window` only in non-production contexts so WDIO E2E
+// specs can read Redux state directly.
// to assert backing-state changes (see app/test/e2e/specs/*.spec.ts). The store
// holds no secrets that aren't already in the renderer's memory; this only
// surfaces the existing handle under a stable, namespaced key.
-if (typeof window !== 'undefined') {
- (window as unknown as { __OPENHUMAN_STORE__?: typeof store }).__OPENHUMAN_STORE__ = store;
+if (typeof window !== 'undefined' && IS_DEV) {
+ Object.defineProperty(window as Window & { __OPENHUMAN_STORE__?: typeof store }, '__OPENHUMAN_STORE__', {
+ value: store,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ });
}As per coding guidelines: “Frontend config is centralized in 🤖 Prompt for AI Agents |
||
|
|
||
| export type RootState = ReturnType<typeof store.getState>; | ||
| export type AppDispatch = typeof store.dispatch; | ||
Uh oh!
There was an error while loading. Please reload this page.