@sanity/sdk-react 0.0.0-alpha.3 → 0.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +78 -28
  2. package/dist/_chunks-es/context.js +8 -0
  3. package/dist/_chunks-es/context.js.map +1 -0
  4. package/dist/_chunks-es/useLogOut.js +11 -11
  5. package/dist/_chunks-es/useLogOut.js.map +1 -1
  6. package/dist/components.d.ts +11 -186
  7. package/dist/components.js +52 -198
  8. package/dist/components.js.map +1 -1
  9. package/dist/context.d.ts +39 -0
  10. package/dist/context.js +5 -0
  11. package/dist/context.js.map +1 -0
  12. package/dist/hooks.d.ts +201 -15
  13. package/dist/hooks.js +80 -11
  14. package/dist/hooks.js.map +1 -1
  15. package/package.json +17 -15
  16. package/src/_exports/components.ts +2 -13
  17. package/src/_exports/context.ts +2 -0
  18. package/src/_exports/hooks.ts +18 -2
  19. package/src/components/SanityApp.test.tsx +54 -0
  20. package/src/components/SanityApp.tsx +26 -0
  21. package/src/components/auth/AuthBoundary.test.tsx +5 -18
  22. package/src/components/auth/AuthBoundary.tsx +2 -2
  23. package/src/components/auth/Login.test.tsx +3 -17
  24. package/src/components/auth/Login.tsx +25 -16
  25. package/src/components/auth/LoginCallback.test.tsx +2 -17
  26. package/src/components/auth/LoginCallback.tsx +6 -4
  27. package/src/components/auth/LoginError.test.tsx +2 -17
  28. package/src/components/auth/LoginError.tsx +8 -12
  29. package/src/components/auth/LoginFooter.test.tsx +2 -16
  30. package/src/components/auth/LoginFooter.tsx +11 -18
  31. package/src/components/auth/LoginLayout.test.tsx +2 -16
  32. package/src/components/auth/LoginLayout.tsx +8 -19
  33. package/src/components/auth/authTestHelpers.tsx +18 -0
  34. package/src/{components/context → context}/SanityProvider.test.tsx +1 -1
  35. package/src/hooks/client/useClient.test.tsx +1 -1
  36. package/src/hooks/comlink/useFrameConnection.test.tsx +122 -0
  37. package/src/hooks/comlink/useFrameConnection.ts +111 -0
  38. package/src/hooks/comlink/useWindowConnection.test.ts +94 -0
  39. package/src/hooks/comlink/useWindowConnection.ts +82 -0
  40. package/src/hooks/context/useSanityInstance.test.tsx +1 -1
  41. package/src/hooks/context/useSanityInstance.ts +2 -2
  42. package/src/hooks/documentCollection/useDocuments.ts +53 -6
  43. package/src/hooks/helpers/createCallbackHook.tsx +1 -1
  44. package/src/hooks/helpers/createStateSourceHook.tsx +1 -1
  45. package/src/hooks/preview/usePreview.test.tsx +17 -8
  46. package/src/hooks/preview/usePreview.tsx +52 -7
  47. package/src/vite-env.d.ts +10 -0
  48. package/dist/assets/bundle-CcAyERuZ.css +0 -11
  49. package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +0 -113
  50. package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +0 -42
  51. package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +0 -21
  52. package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +0 -105
  53. package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +0 -42
  54. package/src/components/DocumentListLayout/DocumentListLayout.tsx +0 -12
  55. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +0 -49
  56. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +0 -39
  57. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +0 -30
  58. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +0 -171
  59. package/src/components/Login/LoginLinks.test.tsx +0 -100
  60. package/src/components/Login/LoginLinks.tsx +0 -73
  61. package/src/css/css.config.js +0 -220
  62. package/src/css/paramour.css +0 -2347
  63. package/src/css/styles.css +0 -11
  64. /package/src/{components/context → context}/SanityProvider.tsx +0 -0
@@ -1,13 +1,10 @@
1
- import {AuthStateType, createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
4
- import {render, screen, waitFor} from '@testing-library/react'
5
- import React from 'react'
1
+ import {AuthStateType} from '@sanity/sdk'
2
+ import {useAuthState} from '@sanity/sdk-react/hooks'
3
+ import {screen, waitFor} from '@testing-library/react'
6
4
  import {beforeEach, describe, expect, it, type MockInstance, vi} from 'vitest'
7
5
 
8
- import {useAuthState} from '../../hooks/auth/useAuthState'
9
- import {SanityProvider} from '../context/SanityProvider'
10
6
  import {AuthBoundary} from './AuthBoundary'
7
+ import {renderWithWrappers} from './authTestHelpers'
11
8
 
12
9
  // Mock hooks
13
10
  vi.mock('../../hooks/auth/useAuthState', () => ({
@@ -38,16 +35,6 @@ vi.mock('./AuthError', async (importOriginal) => {
38
35
  }
39
36
  })
40
37
 
41
- const theme = buildTheme({})
42
- const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
43
- const renderWithWrappers = (ui: React.ReactElement) => {
44
- return render(
45
- <ThemeProvider theme={theme}>
46
- <SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
47
- </ThemeProvider>,
48
- )
49
- }
50
-
51
38
  describe('AuthBoundary', () => {
52
39
  let consoleErrorSpy: MockInstance
53
40
  beforeEach(() => {
@@ -67,7 +54,7 @@ describe('AuthBoundary', () => {
67
54
  renderWithWrappers(<AuthBoundary>Protected Content</AuthBoundary>)
68
55
 
69
56
  // The login screen should show "Choose login provider" by default
70
- expect(screen.getByText('Choose login provider')).toBeInTheDocument()
57
+ expect(screen.getByText('Choose login provider:')).toBeInTheDocument()
71
58
  expect(screen.queryByText('Protected Content')).not.toBeInTheDocument()
72
59
  })
73
60
 
@@ -7,12 +7,12 @@ import {AuthError} from './AuthError'
7
7
  import {Login} from './Login'
8
8
  import {LoginCallback} from './LoginCallback'
9
9
  import {LoginError, type LoginErrorProps} from './LoginError'
10
- import type {LoginLayoutProps} from './LoginLayout'
10
+ import {type LoginLayoutProps} from './LoginLayout'
11
11
 
12
12
  /**
13
13
  * @alpha
14
14
  */
15
- export interface AuthBoundaryProps extends LoginLayoutProps {
15
+ interface AuthBoundaryProps extends LoginLayoutProps {
16
16
  /**
17
17
  * Custom component to render the login screen.
18
18
  * Receives all login layout props. Defaults to {@link Login}.
@@ -1,11 +1,7 @@
1
- import {createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
4
- import {render, screen} from '@testing-library/react'
5
- import React from 'react'
1
+ import {screen} from '@testing-library/react'
6
2
  import {describe, expect, it, vi} from 'vitest'
7
3
 
8
- import {SanityProvider} from '../context/SanityProvider'
4
+ import {renderWithWrappers} from './authTestHelpers'
9
5
  import {Login} from './Login'
10
6
 
11
7
  vi.mock('../../hooks/auth/useLoginUrls', () => ({
@@ -15,20 +11,10 @@ vi.mock('../../hooks/auth/useLoginUrls', () => ({
15
11
  ]),
16
12
  }))
17
13
 
18
- const theme = buildTheme({})
19
- const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
20
- const renderWithWrappers = (ui: React.ReactElement) => {
21
- return render(
22
- <ThemeProvider theme={theme}>
23
- <SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
24
- </ThemeProvider>,
25
- )
26
- }
27
-
28
14
  describe('Login', () => {
29
15
  it('renders login providers', () => {
30
16
  renderWithWrappers(<Login />)
31
- expect(screen.getByText('Choose login provider')).toBeInTheDocument()
17
+ expect(screen.getByText('Choose login provider:')).toBeInTheDocument()
32
18
  expect(screen.getByRole('link', {name: 'Provider A'})).toHaveAttribute(
33
19
  'href',
34
20
  'https://provider-a.com/auth',
@@ -1,4 +1,4 @@
1
- import {Button, Flex, Heading, Spinner} from '@sanity/ui'
1
+ import {Box, Button, Flex, Heading, Spinner, Stack} from '@sanity/ui'
2
2
  import {type JSX, Suspense} from 'react'
3
3
 
4
4
  import {useLoginUrls} from '../../hooks/auth/useLoginUrls'
@@ -9,25 +9,26 @@ import {LoginLayout, type LoginLayoutProps} from './LoginLayout'
9
9
  * Renders a list of login options with a loading fallback while providers load.
10
10
  *
11
11
  * @alpha
12
+ * @internal
12
13
  */
13
14
  export function Login({header, footer}: LoginLayoutProps): JSX.Element {
14
15
  return (
15
16
  <LoginLayout header={header} footer={footer}>
16
- <Flex direction="column" gap={4}>
17
- <Heading as="h1" size={1} align="center">
18
- Choose login provider
19
- </Heading>
17
+ <Heading as="h6" align="center">
18
+ Choose login provider:
19
+ </Heading>
20
20
 
21
- <Suspense
22
- fallback={
23
- <Flex align="center" justify="center" style={{height: '123px'}}>
21
+ <Suspense
22
+ fallback={
23
+ <Box padding={5}>
24
+ <Flex align="center" justify="center">
24
25
  <Spinner />
25
26
  </Flex>
26
- }
27
- >
28
- <Providers />
29
- </Suspense>
30
- </Flex>
27
+ </Box>
28
+ }
29
+ >
30
+ <Providers />
31
+ </Suspense>
31
32
  </LoginLayout>
32
33
  )
33
34
  }
@@ -36,10 +37,18 @@ function Providers() {
36
37
  const loginUrls = useLoginUrls()
37
38
 
38
39
  return (
39
- <Flex direction="column" gap={3}>
40
+ <Stack space={3} marginY={5}>
40
41
  {loginUrls.map(({title, url}) => (
41
- <Button key={url} text={title} as="a" href={url} mode="ghost" />
42
+ <Button
43
+ key={url}
44
+ as="a"
45
+ href={url}
46
+ mode="ghost"
47
+ text={title}
48
+ textAlign="center"
49
+ fontSize={2}
50
+ ></Button>
42
51
  ))}
43
- </Flex>
52
+ </Stack>
44
53
  )
45
54
  }
@@ -1,22 +1,7 @@
1
- import {createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
4
- import {render, screen, waitFor} from '@testing-library/react'
5
- import React from 'react'
1
+ import {screen, waitFor} from '@testing-library/react'
6
2
  import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest'
7
3
 
8
- import {SanityProvider} from '../context/SanityProvider'
9
-
10
- const theme = buildTheme({})
11
- const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
12
-
13
- const renderWithWrappers = (ui: React.ReactElement) => {
14
- return render(
15
- <ThemeProvider theme={theme}>
16
- <SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
17
- </ThemeProvider>,
18
- )
19
- }
4
+ import {renderWithWrappers} from './authTestHelpers'
20
5
 
21
6
  // Mock `useHandleCallback`
22
7
  vi.mock('../../hooks/auth/useHandleCallback', () => ({
@@ -1,4 +1,4 @@
1
- import {Flex, Spinner, Text} from '@sanity/ui'
1
+ import {Flex, Heading, Spinner} from '@sanity/ui'
2
2
  import {useEffect} from 'react'
3
3
 
4
4
  import {useHandleCallback} from '../../hooks/auth/useHandleCallback'
@@ -28,9 +28,11 @@ export function LoginCallback({header, footer}: LoginLayoutProps): React.ReactNo
28
28
 
29
29
  return (
30
30
  <LoginLayout header={header} footer={footer}>
31
- <Flex direction="column" justify="center" align="center" gap={4} style={{margin: 'auto'}}>
32
- <Text size={1}>Logging you in…</Text>
33
- <Spinner size={4} />
31
+ <Heading as="h6" align="center">
32
+ Logging you in
33
+ </Heading>
34
+ <Flex paddingY={5} align="center" justify="center">
35
+ <Spinner />
34
36
  </Flex>
35
37
  </LoginLayout>
36
38
  )
@@ -1,29 +1,14 @@
1
- import {createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
4
- import {fireEvent, render, screen, waitFor} from '@testing-library/react'
5
- import React from 'react'
1
+ import {fireEvent, screen, waitFor} from '@testing-library/react'
6
2
  import {describe, expect, it, vi} from 'vitest'
7
3
 
8
- import {SanityProvider} from '../context/SanityProvider'
9
4
  import {AuthError} from './AuthError'
5
+ import {renderWithWrappers} from './authTestHelpers'
10
6
  import {LoginError} from './LoginError'
11
7
 
12
8
  vi.mock('../../hooks/auth/useLogOut', () => ({
13
9
  useLogOut: vi.fn(() => async () => {}),
14
10
  }))
15
11
 
16
- const theme = buildTheme({})
17
- const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
18
-
19
- const renderWithWrappers = (ui: React.ReactElement) => {
20
- return render(
21
- <ThemeProvider theme={theme}>
22
- <SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
23
- </ThemeProvider>,
24
- )
25
- }
26
-
27
12
  describe('LoginError', () => {
28
13
  it('shows authentication error and retry button', async () => {
29
14
  const mockReset = vi.fn()
@@ -1,4 +1,4 @@
1
- import {Button, Flex, Text} from '@sanity/ui'
1
+ import {Button, Heading, Stack, Text} from '@sanity/ui'
2
2
  import {useCallback} from 'react'
3
3
  import {type FallbackProps} from 'react-error-boundary'
4
4
 
@@ -33,17 +33,13 @@ export function LoginError({
33
33
 
34
34
  return (
35
35
  <LoginLayout header={header} footer={footer}>
36
- <Flex direction="column" gap={4} style={{margin: 'auto'}}>
37
- <Flex direction="column" gap={3}>
38
- <Text as="h2" align="center" weight="bold" size={3}>
39
- Authentication Error
40
- </Text>
41
- <Text size={1} align="center">
42
- Please try again or contact support if the problem persists.
43
- </Text>
44
- </Flex>
45
- <Button text="Retry" tone="primary" onClick={handleRetry} />
46
- </Flex>
36
+ <Stack space={5} marginBottom={5}>
37
+ <Heading as="h6" align="center">
38
+ Authentication Error
39
+ </Heading>
40
+ <Text align="center">Please try again or contact support if the problem persists.</Text>
41
+ <Button mode="ghost" onClick={handleRetry} text="Retry" fontSize={2} />
42
+ </Stack>
47
43
  </LoginLayout>
48
44
  )
49
45
  }
@@ -1,23 +1,9 @@
1
- import {createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
4
- import {render, screen} from '@testing-library/react'
5
- import React from 'react'
1
+ import {screen} from '@testing-library/react'
6
2
  import {describe, expect, it} from 'vitest'
7
3
 
8
- import {SanityProvider} from '../context/SanityProvider'
4
+ import {renderWithWrappers} from './authTestHelpers'
9
5
  import {LoginFooter} from './LoginFooter'
10
6
 
11
- const theme = buildTheme({})
12
- const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
13
- const renderWithWrappers = (ui: React.ReactElement) => {
14
- return render(
15
- <ThemeProvider theme={theme}>
16
- <SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
17
- </ThemeProvider>,
18
- )
19
- }
20
-
21
7
  describe('LoginFooter', () => {
22
8
  it('renders footer links', () => {
23
9
  renderWithWrappers(<LoginFooter />)
@@ -1,6 +1,5 @@
1
1
  import {SanityLogo} from '@sanity/logos'
2
- import {Flex, Text} from '@sanity/ui'
3
- import {Fragment} from 'react'
2
+ import {Box, Flex, Inline, Text} from '@sanity/ui'
4
3
 
5
4
  const LINKS = [
6
5
  {
@@ -33,15 +32,15 @@ const LINKS = [
33
32
  */
34
33
  export function LoginFooter(): React.ReactNode {
35
34
  return (
36
- <Flex direction="column" gap={4} justify="center" align="center" paddingTop={2}>
37
- <Text size={3}>
35
+ <Box>
36
+ <Flex justify="center">
38
37
  <SanityLogo />
39
- </Text>
38
+ </Flex>
40
39
 
41
- <Flex align="center" gap={2}>
42
- {LINKS.map((link, index) => (
43
- <Fragment key={link.title}>
44
- <Text muted size={1}>
40
+ <Flex justify="center">
41
+ <Inline space={2} paddingY={3}>
42
+ {LINKS.map((link) => (
43
+ <Text size={0} key={link.url}>
45
44
  <a
46
45
  href={link.url}
47
46
  target="_blank"
@@ -51,15 +50,9 @@ export function LoginFooter(): React.ReactNode {
51
50
  {link.title}
52
51
  </a>
53
52
  </Text>
54
-
55
- {index < LINKS.length - 1 && (
56
- <Text size={1} muted>
57
-
58
- </Text>
59
- )}
60
- </Fragment>
61
- ))}
53
+ ))}
54
+ </Inline>
62
55
  </Flex>
63
- </Flex>
56
+ </Box>
64
57
  )
65
58
  }
@@ -1,23 +1,9 @@
1
- import {createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
4
- import {render, screen} from '@testing-library/react'
5
- import React from 'react'
1
+ import {screen} from '@testing-library/react'
6
2
  import {describe, expect, it} from 'vitest'
7
3
 
8
- import {SanityProvider} from '../context/SanityProvider'
4
+ import {renderWithWrappers} from './authTestHelpers'
9
5
  import {LoginLayout} from './LoginLayout'
10
6
 
11
- const theme = buildTheme({})
12
- const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
13
- const renderWithWrappers = (ui: React.ReactElement) => {
14
- return render(
15
- <ThemeProvider theme={theme}>
16
- <SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
17
- </ThemeProvider>,
18
- )
19
- }
20
-
21
7
  describe('LoginLayout', () => {
22
8
  it('renders header, children, and footer', () => {
23
9
  renderWithWrappers(
@@ -1,9 +1,10 @@
1
- import {Card, Flex} from '@sanity/ui'
1
+ import {Card, Container} from '@sanity/ui'
2
2
 
3
3
  import {LoginFooter} from './LoginFooter'
4
4
 
5
5
  /**
6
6
  * @alpha
7
+ * @internal
7
8
  */
8
9
  export interface LoginLayoutProps {
9
10
  /** Optional header content rendered at top of card */
@@ -56,26 +57,14 @@ export function LoginLayout({
56
57
  header,
57
58
  }: LoginLayoutProps): React.ReactNode {
58
59
  return (
59
- <div style={{width: '100%', display: 'flex'}}>
60
- <Flex direction="column" gap={4} style={{width: '320px', margin: 'auto', display: 'flex'}}>
61
- <Card border radius={2} paddingY={4}>
62
- <Flex direction="column" gap={4}>
63
- {header && (
64
- <Card borderBottom paddingX={4} paddingBottom={3}>
65
- {header}
66
- </Card>
67
- )}
60
+ <Container width={0}>
61
+ <Card shadow={1} radius={2} padding={4}>
62
+ {header && header}
68
63
 
69
- {children && (
70
- <Flex paddingX={4} direction="column" style={{minHeight: '154px'}}>
71
- {children}
72
- </Flex>
73
- )}
74
- </Flex>
75
- </Card>
64
+ {children && children}
76
65
 
77
66
  {footer}
78
- </Flex>
79
- </div>
67
+ </Card>
68
+ </Container>
80
69
  )
81
70
  }
@@ -0,0 +1,18 @@
1
+ import {createSanityInstance} from '@sanity/sdk'
2
+ import {ThemeProvider} from '@sanity/ui'
3
+ import {buildTheme} from '@sanity/ui/theme'
4
+ import {render, type RenderResult} from '@testing-library/react'
5
+ import React from 'react'
6
+
7
+ import {SanityProvider} from '../../context/SanityProvider'
8
+
9
+ const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
10
+ const theme = buildTheme()
11
+
12
+ export const renderWithWrappers = (ui: React.ReactElement): RenderResult => {
13
+ return render(
14
+ <SanityProvider sanityInstance={sanityInstance}>
15
+ <ThemeProvider theme={theme}>{ui}</ThemeProvider>
16
+ </SanityProvider>,
17
+ )
18
+ }
@@ -2,7 +2,7 @@ import {createSanityInstance} from '@sanity/sdk'
2
2
  import {render} from '@testing-library/react'
3
3
  import {describe, expect, it} from 'vitest'
4
4
 
5
- import {useSanityInstance} from '../../hooks/context/useSanityInstance'
5
+ import {useSanityInstance} from '../hooks/context/useSanityInstance'
6
6
  import {SanityProvider} from './SanityProvider'
7
7
 
8
8
  describe('SanityProvider', () => {
@@ -1,6 +1,6 @@
1
1
  import {type SanityClient} from '@sanity/client'
2
2
  import {act} from '@testing-library/react'
3
- import type {Subscribable, Subscriber} from 'rxjs'
3
+ import {type Subscribable, type Subscriber} from 'rxjs'
4
4
  import {beforeEach, describe, expect, it, vi} from 'vitest'
5
5
 
6
6
  import {renderHook} from '../../../test/test-utils'
@@ -0,0 +1,122 @@
1
+ import {type ChannelInstance, type Controller} from '@sanity/comlink'
2
+ import {beforeEach, describe, expect, it, vi} from 'vitest'
3
+
4
+ import {renderHook} from '../../../test/test-utils'
5
+ import {useFrameConnection} from './useFrameConnection'
6
+
7
+ vi.mock(import('@sanity/sdk'), async (importOriginal) => {
8
+ const actual = await importOriginal()
9
+ return {
10
+ ...actual,
11
+ getOrCreateChannel: vi.fn(),
12
+ getOrCreateController: vi.fn(),
13
+ releaseChannel: vi.fn(),
14
+ }
15
+ })
16
+
17
+ const {getOrCreateChannel, getOrCreateController, releaseChannel} = await import('@sanity/sdk')
18
+
19
+ interface TestControllerMessage {
20
+ type: 'TEST_MESSAGE'
21
+ data: {
22
+ someData: string
23
+ }
24
+ }
25
+
26
+ interface TestNodeMessage {
27
+ type: 'NODE_MESSAGE'
28
+ data: {
29
+ someData: string
30
+ }
31
+ }
32
+
33
+ function createMockChannel() {
34
+ return {
35
+ on: vi.fn(() => () => {}),
36
+ post: vi.fn(),
37
+ stop: vi.fn(),
38
+ } as unknown as ChannelInstance<TestControllerMessage, TestNodeMessage>
39
+ }
40
+
41
+ describe('useFrameController', () => {
42
+ let channel: ChannelInstance<TestControllerMessage, TestNodeMessage>
43
+ let controller: Controller
44
+ let removeTargetMock: ReturnType<typeof vi.fn>
45
+
46
+ beforeEach(() => {
47
+ channel = createMockChannel()
48
+ removeTargetMock = vi.fn()
49
+ controller = {
50
+ addTarget: vi.fn(() => removeTargetMock),
51
+ destroy: vi.fn(),
52
+ } as unknown as Controller
53
+ vi.mocked(getOrCreateChannel).mockReturnValue(channel)
54
+ vi.mocked(getOrCreateController).mockReturnValue(controller)
55
+ })
56
+
57
+ it('should register and execute message handlers', () => {
58
+ const mockHandler = vi.fn()
59
+ const mockData = {someData: 'test'}
60
+ renderHook(() =>
61
+ useFrameConnection({
62
+ name: 'test',
63
+ connectTo: 'iframe',
64
+ targetOrigin: '*',
65
+ onMessage: {
66
+ TEST_MESSAGE: mockHandler,
67
+ },
68
+ }),
69
+ )
70
+
71
+ const onCallback = vi.mocked(channel.on).mock.calls[0][1]
72
+ onCallback(mockData)
73
+ expect(mockHandler).toHaveBeenCalledWith(mockData)
74
+ })
75
+
76
+ it('should handle connecting frames and cleanup on disconnect', () => {
77
+ const {result} = renderHook(() =>
78
+ useFrameConnection({
79
+ name: 'test',
80
+ connectTo: 'iframe',
81
+ targetOrigin: '*',
82
+ }),
83
+ )
84
+
85
+ const mockWindow = {} as Window
86
+ const cleanup = result.current.connect(mockWindow)
87
+
88
+ expect(controller.addTarget).toHaveBeenCalledWith(mockWindow)
89
+
90
+ // Test cleanup
91
+ cleanup()
92
+ expect(removeTargetMock).toHaveBeenCalled()
93
+ })
94
+
95
+ it('should send messages correctly', () => {
96
+ const {result} = renderHook(() =>
97
+ useFrameConnection<TestControllerMessage, TestNodeMessage>({
98
+ name: 'test',
99
+ connectTo: 'iframe',
100
+ targetOrigin: '*',
101
+ }),
102
+ )
103
+
104
+ const mockData = {someData: 'test'}
105
+ result.current.sendMessage('TEST_MESSAGE', mockData)
106
+
107
+ expect(channel.post).toHaveBeenCalledWith('TEST_MESSAGE', mockData)
108
+ })
109
+
110
+ it('should cleanup on unmount', () => {
111
+ const {unmount} = renderHook(() =>
112
+ useFrameConnection({
113
+ name: 'test',
114
+ connectTo: 'iframe',
115
+ targetOrigin: '*',
116
+ }),
117
+ )
118
+
119
+ unmount()
120
+ expect(releaseChannel).toHaveBeenCalled()
121
+ })
122
+ })