-
-
Notifications
You must be signed in to change notification settings - Fork 290
feat: image generation offload - run the Mac's image models from the phone #638
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
base: main
Are you sure you want to change the base?
Changes from all commits
9f3de0e
d48de0f
4b1c450
c256d0f
ce0b1ee
81da658
0445d6f
c61a8e8
b1fb265
ca2b0e2
61f6a28
87adccb
229394f
dc96757
7de2a97
39976f4
19126e3
d5af873
5c8481a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /** | ||
| * Integration (RNTL): CompanionToolsSection - the single home for desktop tools. | ||
| * | ||
| * Proves the moved grant: paired desktops (only desktops) appear in Pro tools with a | ||
| * switch that reflects whether their tools are connected here (grantedByDeviceId), and | ||
| * flipping one calls requestTools(deviceId, next) - the same mesh request the old | ||
| * Devices-row toggle sent. Loaded via a computed path so it skips where pro/ is absent. | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
| import { render, fireEvent } from '@testing-library/react-native'; | ||
|
|
||
| jest.mock('react-native-vector-icons/Feather', () => { | ||
| const { Text } = require('react-native'); | ||
| return ({ name, ...props }: any) => <Text {...props}>{name}</Text>; | ||
| }); | ||
|
|
||
| jest.mock('../../../src/theme', () => ({ | ||
| useTheme: () => ({ | ||
| colors: { | ||
| text: '#000', textMuted: '#999', primary: '#1DB954', surface: '#F5F5F5', border: '#E0E0E0', | ||
| }, | ||
| }), | ||
| })); | ||
|
|
||
| const mockState: { knownDevices: unknown[]; servers: unknown[] } = { knownDevices: [], servers: [] }; | ||
| const mockRequestTools = jest.fn(); | ||
|
|
||
| jest.mock('../../../pro/sync/syncStore', () => ({ | ||
| useSyncStore: (selector: (s: unknown) => unknown) => | ||
| selector({ knownDevices: mockState.knownDevices }), | ||
| })); | ||
| jest.mock('../../../pro/mcp/mcpStore', () => ({ | ||
| useMcpStore: (selector: (s: unknown) => unknown) => selector({ servers: mockState.servers }), | ||
| })); | ||
| jest.mock('../../../pro/mcp/mcpToolGrantService', () => ({ | ||
| requestTools: (...args: unknown[]) => mockRequestTools(...args), | ||
| })); | ||
|
|
||
| type Mod = typeof import('../../../pro/ui/CompanionToolsSection'); | ||
| function load(): Mod | null { | ||
| try { | ||
| return require(['..', '..', '..', 'pro', 'ui', 'CompanionToolsSection'].join('/')); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| const mod = load(); | ||
| const maybe = mod ? describe : describe.skip; | ||
|
|
||
| maybe('CompanionToolsSection', () => { | ||
| const { CompanionToolsSection } = mod!; | ||
|
|
||
| beforeEach(() => { | ||
| mockState.knownDevices = []; | ||
| mockState.servers = []; | ||
| mockRequestTools.mockClear(); | ||
| }); | ||
|
|
||
| it('lists only desktop peers, reflects the grant, and toggles via requestTools', () => { | ||
| mockState.knownDevices = [ | ||
| { id: 'mac1', name: 'My Mac', platform: 'macos' }, | ||
| { id: 'phone1', name: 'My Phone', platform: 'ios' }, | ||
| ]; | ||
| mockState.servers = [{ id: 's1', grantedByDeviceId: 'mac1' }]; | ||
|
|
||
| const { getByTestId, queryByTestId } = render(<CompanionToolsSection />); | ||
| // Desktop shows; a phone peer (serves no tools) is filtered out. | ||
| expect(getByTestId('companion-tools-mac1')).toBeTruthy(); | ||
| expect(queryByTestId('companion-tools-phone1')).toBeNull(); | ||
|
|
||
| const sw = getByTestId('companion-tools-switch-mac1'); | ||
| expect(sw.props.value).toBe(true); // granted -> on | ||
| fireEvent(sw, 'valueChange', false); | ||
| expect(mockRequestTools).toHaveBeenCalledWith('mac1', false); | ||
| }); | ||
|
|
||
| it('renders nothing when there are no paired desktops', () => { | ||
| mockState.knownDevices = [{ id: 'phone1', name: 'Phone', platform: 'android' }]; | ||
| const { toJSON } = render(<CompanionToolsSection />); | ||
| expect(toJSON()).toBeNull(); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| /** | ||
| * Integration (RNTL): PairingCodeSheet scan-to-pair. | ||
| * | ||
| * Guards the approved behavior change: a paired-code sheet can be filled by scanning | ||
| * the other device's QR, not just by typing. A decoded QR carrying a valid pairing | ||
| * code lands on the SAME onPair (syncService.pair) as the typed path, and a QR that | ||
| * is not a pairing code is ignored so the scanner keeps looking. | ||
| * | ||
| * Lives in the private pro/ submodule, loaded via a computed path so the suite skips | ||
| * in open-core CI where pro/ is absent. | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
| import { render, fireEvent, act } from '@testing-library/react-native'; | ||
|
|
||
| jest.mock('react-native-vector-icons/Feather', () => { | ||
| const { Text } = require('react-native'); | ||
| return ({ name, ...props }: any) => <Text {...props}>{name}</Text>; | ||
| }); | ||
|
|
||
| // The sheet is a modal wrapper; render its children inline (respecting `visible`, as | ||
| // the real one does) so the test can drive the content and observe it hiding while | ||
| // the scanner is open. | ||
| jest.mock('@offgrid/core/components/AppSheet', () => ({ | ||
| AppSheet: ({ visible, children }: { visible: boolean; children: React.ReactNode }) => | ||
| visible ? children : null, | ||
| })); | ||
|
|
||
| jest.mock('../../../src/theme', () => { | ||
| const colors = { | ||
| text: '#000', textMuted: '#999', primary: '#1DB954', error: '#F00', | ||
| background: '#FFF', surface: '#F5F5F5', border: '#E0E0E0', | ||
| }; | ||
| const shadows = { small: {}, medium: {}, large: {} }; | ||
| return { | ||
| useTheme: () => ({ colors, shadows, isDark: false }), | ||
| useThemedStyles: (fn: any) => fn(colors, shadows), | ||
| }; | ||
| }); | ||
|
|
||
| // vision-camera is globally stubbed in jest.setup; capture the scan config here so | ||
| // the test can simulate a decoded QR frame. | ||
| const visionCamera = require('react-native-vision-camera'); | ||
| let scanConfig: { onCodeScanned: (codes: { value?: string }[]) => void } | null = null; | ||
|
|
||
| type SheetModule = typeof import('../../../pro/ui/SyncScreen/PairingCodeSheet'); | ||
|
|
||
| function load(): SheetModule | null { | ||
| try { | ||
| return require(['..', '..', '..', 'pro', 'ui', 'SyncScreen', 'PairingCodeSheet'].join('/')); | ||
| } catch { | ||
| return null; | ||
| } | ||
|
Comment on lines
+48
to
+53
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Do not skip module evaluation failures. The broad Detect the absent submodule before 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| const mod = load(); | ||
| const maybe = mod ? describe : describe.skip; | ||
|
|
||
| // A valid code: every character is in the pairing alphabet. | ||
| const VALID_QR = 'ABCD2345'; | ||
|
|
||
| maybe('PairingCodeSheet scan-to-pair', () => { | ||
| const { PairingCodeSheet } = mod!; | ||
|
|
||
| const baseProps = () => ({ | ||
| visible: true, | ||
| deviceName: 'Studio Mac', | ||
| confirmLabel: 'Pair', | ||
| testIDPrefix: 'sync-test', | ||
| onClose: jest.fn(), | ||
| onPair: jest.fn().mockResolvedValue(undefined), | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| scanConfig = null; | ||
| jest.spyOn(visionCamera, 'useCodeScanner').mockImplementation((cfg: any) => { | ||
| scanConfig = cfg; | ||
| return cfg; | ||
| }); | ||
| }); | ||
|
|
||
| it('offers a Scan button that opens the camera scanner', () => { | ||
| const { getByTestId, queryByText, getByText } = render( | ||
| <PairingCodeSheet {...baseProps()} />, | ||
| ); | ||
| expect(queryByText('Camera access needed')).toBeNull(); | ||
| fireEvent.press(getByTestId('sync-test-scan')); | ||
| // Global vision-camera mock reports no permission, so the scanner asks for it - | ||
| // proof the scanner surface mounted. | ||
| expect(getByText('Camera access needed')).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('hides the pairing sheet while the scanner is open (one modal at a time)', () => { | ||
| // iOS presents one modal at a time; the sheet must yield so the scanner can show. | ||
| const { getByTestId, queryByTestId } = render(<PairingCodeSheet {...baseProps()} />); | ||
| expect(queryByTestId('sync-test-input')).toBeTruthy(); | ||
| fireEvent.press(getByTestId('sync-test-scan')); | ||
| expect(queryByTestId('sync-test-input')).toBeNull(); | ||
| }); | ||
|
|
||
| it('pairs from a scanned QR via the same onPair as typing', async () => { | ||
| const props = baseProps(); | ||
| const { getByTestId } = render(<PairingCodeSheet {...props} />); | ||
| fireEvent.press(getByTestId('sync-test-scan')); | ||
| await act(async () => { | ||
| scanConfig!.onCodeScanned([{ value: VALID_QR }]); | ||
| }); | ||
| expect(props.onPair).toHaveBeenCalledWith(VALID_QR); | ||
| }); | ||
|
|
||
| it('ignores a QR that is not a pairing code', async () => { | ||
| const props = baseProps(); | ||
| const { getByTestId } = render(<PairingCodeSheet {...props} />); | ||
| fireEvent.press(getByTestId('sync-test-scan')); | ||
| await act(async () => { | ||
| scanConfig!.onCodeScanned([{ value: 'https://example.com/not-a-code' }]); | ||
| }); | ||
| expect(props.onPair).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: off-grid-ai/OGAM
Length of output: 18332
🏁 Script executed:
Repository: off-grid-ai/OGAM
Length of output: 13423
🌐 Web query:
Jest 29 describe.skip callback is executed during test definition suite collection official documentation source💡 Result:
Yes, when you use describe.skip, the callback function passed to that describe block is executed during Jest's test definition/collection phase [1][2][3]. Jest executes all describe block handlers in a test file to collect and build the test suite structure [4]. Consequently, any code written directly within the body of a describe.skip block (outside of test or hook functions) will run during this collection phase [1][2][3]. Official documentation advises that because the describe block itself still runs, you should place any setup logic you intend to skip inside beforeAll or beforeEach blocks, rather than directly in the describe block body [3][5][6]. Tests and hooks defined within a skipped describe block are prevented from executing during the actual test run phase [1][2][7].
Citations:
Guard
modbefore destructuring it.When
pro/is absent,load()returnsnull. Jest executes thedescribe.skipcallback during collection, so line 53 throws aTypeErrorbefore the suite skips. Addif (!mod) return;before destructuring.🤖 Prompt for AI Agents