one 1.21.6 → 1.21.8
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.
- package/dist/cjs/cli/dev.native.js.map +1 -1
- package/dist/cjs/cli.cjs +0 -3
- package/dist/cjs/cli.native.js +0 -3
- package/dist/cjs/cli.native.js.map +1 -1
- package/dist/cjs/image/getImageData.cjs +1 -1
- package/dist/cjs/image/getImageData.native.js +1 -1
- package/dist/cjs/image/getImageData.native.js.map +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/layouts/Stack.cjs +5 -5
- package/dist/cjs/layouts/Stack.native.js +5 -5
- package/dist/cjs/layouts/Stack.native.js.map +1 -1
- package/dist/cjs/layouts/stack-utils/StackScreen.cjs +3 -0
- package/dist/cjs/layouts/stack-utils/StackScreen.native.js +3 -0
- package/dist/cjs/layouts/stack-utils/StackScreen.native.js.map +1 -1
- package/dist/cjs/layouts/stack-utils/StackToolbar.cjs +419 -0
- package/dist/cjs/layouts/stack-utils/StackToolbar.native.js +453 -0
- package/dist/cjs/layouts/stack-utils/StackToolbar.native.js.map +1 -0
- package/dist/cjs/layouts/stack-utils/StackToolbar.types.cjs +18 -0
- package/dist/cjs/layouts/stack-utils/StackToolbar.types.native.js +21 -0
- package/dist/cjs/layouts/stack-utils/StackToolbar.types.native.js.map +1 -0
- package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.runtime.test.cjs +63 -0
- package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js +73 -0
- package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js.map +1 -0
- package/dist/cjs/layouts/stack-utils/__tests__/composition.test.cjs +205 -0
- package/dist/cjs/layouts/stack-utils/__tests__/composition.test.native.js +220 -0
- package/dist/cjs/layouts/stack-utils/__tests__/composition.test.native.js.map +1 -1
- package/dist/cjs/layouts/stack-utils/index.cjs +2 -0
- package/dist/cjs/layouts/stack-utils/index.native.js +2 -0
- package/dist/cjs/layouts/stack-utils/index.native.js.map +1 -1
- package/dist/cjs/layouts/withLayoutContext.cjs +39 -8
- package/dist/cjs/layouts/withLayoutContext.native.js +43 -8
- package/dist/cjs/layouts/withLayoutContext.native.js.map +1 -1
- package/dist/cjs/layouts/withLayoutContext.test.cjs +96 -0
- package/dist/cjs/layouts/withLayoutContext.test.native.js +100 -0
- package/dist/cjs/layouts/withLayoutContext.test.native.js.map +1 -0
- package/dist/cjs/router/Route.cjs +3 -0
- package/dist/cjs/router/Route.native.js +3 -0
- package/dist/cjs/router/Route.native.js.map +1 -1
- package/dist/cjs/router/protectedRoutes.test.cjs +22 -0
- package/dist/cjs/router/protectedRoutes.test.native.js +25 -0
- package/dist/cjs/router/protectedRoutes.test.native.js.map +1 -0
- package/dist/cjs/router/router.cjs +26 -8
- package/dist/cjs/router/router.native.js +26 -8
- package/dist/cjs/router/router.native.js.map +1 -1
- package/dist/cjs/router/useScreens.cjs +38 -11
- package/dist/cjs/router/useScreens.native.js +39 -11
- package/dist/cjs/router/useScreens.native.js.map +1 -1
- package/dist/cjs/router/useScreens.test.cjs +134 -0
- package/dist/cjs/router/useScreens.test.native.js +163 -0
- package/dist/cjs/router/useScreens.test.native.js.map +1 -0
- package/dist/cjs/views/Navigator.cjs +14 -6
- package/dist/cjs/views/Navigator.native.js +14 -6
- package/dist/cjs/views/Navigator.native.js.map +1 -1
- package/dist/cjs/views/Protected.native.js.map +1 -1
- package/dist/cjs/views/SuspenseFallback.cjs +18 -0
- package/dist/cjs/views/SuspenseFallback.native.js +21 -0
- package/dist/cjs/views/SuspenseFallback.native.js.map +1 -0
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +47 -7
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +47 -7
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.cjs +15 -6
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.native.js +15 -6
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.native.js.map +1 -1
- package/dist/esm/cli/dev.mjs.map +1 -1
- package/dist/esm/cli/dev.native.js.map +1 -1
- package/dist/esm/cli.mjs +0 -3
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/cli.native.js +0 -3
- package/dist/esm/cli.native.js.map +1 -1
- package/dist/esm/image/getImageData.mjs +1 -1
- package/dist/esm/image/getImageData.mjs.map +1 -1
- package/dist/esm/image/getImageData.native.js +1 -1
- package/dist/esm/image/getImageData.native.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/layouts/Stack.mjs +6 -6
- package/dist/esm/layouts/Stack.mjs.map +1 -1
- package/dist/esm/layouts/Stack.native.js +6 -6
- package/dist/esm/layouts/Stack.native.js.map +1 -1
- package/dist/esm/layouts/stack-utils/StackScreen.mjs +3 -0
- package/dist/esm/layouts/stack-utils/StackScreen.mjs.map +1 -1
- package/dist/esm/layouts/stack-utils/StackScreen.native.js +3 -0
- package/dist/esm/layouts/stack-utils/StackScreen.native.js.map +1 -1
- package/dist/esm/layouts/stack-utils/StackToolbar.mjs +384 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.mjs.map +1 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.native.js +416 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.native.js.map +1 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.types.mjs +2 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.types.mjs.map +1 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.types.native.js +2 -0
- package/dist/esm/layouts/stack-utils/StackToolbar.types.native.js.map +1 -0
- package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.mjs +40 -0
- package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.mjs.map +1 -0
- package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js +47 -0
- package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js.map +1 -0
- package/dist/esm/layouts/stack-utils/__tests__/composition.test.mjs +205 -0
- package/dist/esm/layouts/stack-utils/__tests__/composition.test.mjs.map +1 -1
- package/dist/esm/layouts/stack-utils/__tests__/composition.test.native.js +220 -0
- package/dist/esm/layouts/stack-utils/__tests__/composition.test.native.js.map +1 -1
- package/dist/esm/layouts/stack-utils/index.mjs +2 -1
- package/dist/esm/layouts/stack-utils/index.mjs.map +1 -1
- package/dist/esm/layouts/stack-utils/index.native.js +2 -1
- package/dist/esm/layouts/stack-utils/index.native.js.map +1 -1
- package/dist/esm/layouts/withLayoutContext.mjs +41 -11
- package/dist/esm/layouts/withLayoutContext.mjs.map +1 -1
- package/dist/esm/layouts/withLayoutContext.native.js +45 -11
- package/dist/esm/layouts/withLayoutContext.native.js.map +1 -1
- package/dist/esm/layouts/withLayoutContext.test.mjs +73 -0
- package/dist/esm/layouts/withLayoutContext.test.mjs.map +1 -0
- package/dist/esm/layouts/withLayoutContext.test.native.js +74 -0
- package/dist/esm/layouts/withLayoutContext.test.native.js.map +1 -0
- package/dist/esm/router/Route.mjs +3 -1
- package/dist/esm/router/Route.mjs.map +1 -1
- package/dist/esm/router/Route.native.js +3 -1
- package/dist/esm/router/Route.native.js.map +1 -1
- package/dist/esm/router/protectedRoutes.test.mjs +23 -0
- package/dist/esm/router/protectedRoutes.test.mjs.map +1 -0
- package/dist/esm/router/protectedRoutes.test.native.js +23 -0
- package/dist/esm/router/protectedRoutes.test.native.js.map +1 -0
- package/dist/esm/router/router.mjs +26 -9
- package/dist/esm/router/router.mjs.map +1 -1
- package/dist/esm/router/router.native.js +26 -9
- package/dist/esm/router/router.native.js.map +1 -1
- package/dist/esm/router/useScreens.mjs +39 -11
- package/dist/esm/router/useScreens.mjs.map +1 -1
- package/dist/esm/router/useScreens.native.js +40 -11
- package/dist/esm/router/useScreens.native.js.map +1 -1
- package/dist/esm/router/useScreens.test.mjs +105 -0
- package/dist/esm/router/useScreens.test.mjs.map +1 -0
- package/dist/esm/router/useScreens.test.native.js +130 -0
- package/dist/esm/router/useScreens.test.native.js.map +1 -0
- package/dist/esm/views/Navigator.mjs +15 -7
- package/dist/esm/views/Navigator.mjs.map +1 -1
- package/dist/esm/views/Navigator.native.js +15 -7
- package/dist/esm/views/Navigator.native.js.map +1 -1
- package/dist/esm/views/Protected.mjs.map +1 -1
- package/dist/esm/views/Protected.native.js.map +1 -1
- package/dist/esm/views/SuspenseFallback.mjs +2 -0
- package/dist/esm/views/SuspenseFallback.mjs.map +1 -0
- package/dist/esm/views/SuspenseFallback.native.js +2 -0
- package/dist/esm/views/SuspenseFallback.native.js.map +1 -0
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +47 -7
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +47 -7
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.mjs +15 -6
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.native.js +15 -6
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.native.js.map +1 -1
- package/package.json +12 -11
- package/src/cli/dev.ts +0 -1
- package/src/cli.ts +0 -3
- package/src/image/getImageData.ts +2 -2
- package/src/index.ts +12 -0
- package/src/layouts/Stack.tsx +6 -4
- package/src/layouts/stack-utils/README.md +1 -0
- package/src/layouts/stack-utils/StackScreen.tsx +7 -0
- package/src/layouts/stack-utils/StackToolbar.tsx +550 -0
- package/src/layouts/stack-utils/StackToolbar.types.ts +98 -0
- package/src/layouts/stack-utils/__tests__/StackToolbar.runtime.test.tsx +46 -0
- package/src/layouts/stack-utils/__tests__/composition.test.tsx +176 -0
- package/src/layouts/stack-utils/index.tsx +16 -0
- package/src/layouts/withLayoutContext.test.tsx +74 -0
- package/src/layouts/withLayoutContext.tsx +71 -12
- package/src/router/Route.tsx +8 -0
- package/src/router/protectedRoutes.test.ts +35 -0
- package/src/router/router.ts +48 -20
- package/src/router/useScreens.test.tsx +139 -0
- package/src/router/useScreens.tsx +59 -13
- package/src/views/Navigator.tsx +25 -5
- package/src/views/Protected.tsx +3 -0
- package/src/views/SuspenseFallback.ts +7 -0
- package/src/vite/plugins/fileSystemRouterPlugin.test.ts +15 -6
- package/src/vite/plugins/fileSystemRouterPlugin.tsx +52 -7
- package/types/cli/dev.d.ts +0 -1
- package/types/cli/dev.d.ts.map +1 -1
- package/types/image/getImageData.d.ts +1 -1
- package/types/image/getImageData.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/layouts/Stack.d.ts +11 -0
- package/types/layouts/Stack.d.ts.map +1 -1
- package/types/layouts/stack-utils/StackScreen.d.ts.map +1 -1
- package/types/layouts/stack-utils/StackToolbar.d.ts +26 -0
- package/types/layouts/stack-utils/StackToolbar.d.ts.map +1 -0
- package/types/layouts/stack-utils/StackToolbar.types.d.ts +82 -0
- package/types/layouts/stack-utils/StackToolbar.types.d.ts.map +1 -0
- package/types/layouts/stack-utils/index.d.ts +3 -1
- package/types/layouts/stack-utils/index.d.ts.map +1 -1
- package/types/layouts/withLayoutContext.d.ts +7 -0
- package/types/layouts/withLayoutContext.d.ts.map +1 -1
- package/types/layouts/withLayoutContext.test.d.ts +2 -0
- package/types/layouts/withLayoutContext.test.d.ts.map +1 -0
- package/types/router/Route.d.ts +4 -0
- package/types/router/Route.d.ts.map +1 -1
- package/types/router/protectedRoutes.test.d.ts +2 -0
- package/types/router/protectedRoutes.test.d.ts.map +1 -0
- package/types/router/router.d.ts +4 -6
- package/types/router/router.d.ts.map +1 -1
- package/types/router/useScreens.d.ts.map +1 -1
- package/types/router/useScreens.test.d.ts +2 -0
- package/types/router/useScreens.test.d.ts.map +1 -0
- package/types/views/Navigator.d.ts.map +1 -1
- package/types/views/Protected.d.ts +3 -0
- package/types/views/Protected.d.ts.map +1 -1
- package/types/views/SuspenseFallback.d.ts +8 -0
- package/types/views/SuspenseFallback.d.ts.map +1 -0
- package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { ColorValue, ImageSourcePropType, StyleProp, TextStyle } from 'react-native'
|
|
3
|
+
|
|
4
|
+
export type StackToolbarPlacement = 'left' | 'right' | 'bottom'
|
|
5
|
+
|
|
6
|
+
export interface StackToolbarProps {
|
|
7
|
+
children?: ReactNode
|
|
8
|
+
placement?: StackToolbarPlacement
|
|
9
|
+
asChild?: boolean
|
|
10
|
+
/** Android-only. One's Android toolbar host is not implemented yet. */
|
|
11
|
+
disableImePadding?: boolean
|
|
12
|
+
/** Android-only. One's Android toolbar host is not implemented yet. */
|
|
13
|
+
tintColor?: ColorValue
|
|
14
|
+
/** Android-only. One's Android toolbar host is not implemented yet. */
|
|
15
|
+
backgroundColor?: ColorValue
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StackToolbarIconProps {
|
|
19
|
+
src?: ImageSourcePropType
|
|
20
|
+
sf?: string
|
|
21
|
+
xcasset?: string
|
|
22
|
+
renderingMode?: 'template' | 'original'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StackToolbarLabelProps {
|
|
26
|
+
children?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StackToolbarBadgeProps {
|
|
30
|
+
children?: string
|
|
31
|
+
style?: StyleProp<
|
|
32
|
+
Pick<
|
|
33
|
+
TextStyle,
|
|
34
|
+
'fontFamily' | 'fontSize' | 'fontWeight' | 'color' | 'backgroundColor'
|
|
35
|
+
>
|
|
36
|
+
>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface StackToolbarItemProps {
|
|
40
|
+
children?: ReactNode
|
|
41
|
+
icon?: string | ImageSourcePropType
|
|
42
|
+
iconRenderingMode?: 'template' | 'original'
|
|
43
|
+
tintColor?: ColorValue
|
|
44
|
+
hidden?: boolean
|
|
45
|
+
disabled?: boolean
|
|
46
|
+
accessibilityLabel?: string
|
|
47
|
+
accessibilityHint?: string
|
|
48
|
+
hidesSharedBackground?: boolean
|
|
49
|
+
separateBackground?: boolean
|
|
50
|
+
style?: StyleProp<TextStyle>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface StackToolbarButtonProps extends StackToolbarItemProps {
|
|
54
|
+
image?: ImageSourcePropType
|
|
55
|
+
onPress?: () => void
|
|
56
|
+
selected?: boolean
|
|
57
|
+
variant?: 'plain' | 'done' | 'prominent'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface StackToolbarMenuProps extends StackToolbarItemProps {
|
|
61
|
+
image?: ImageSourcePropType
|
|
62
|
+
destructive?: boolean
|
|
63
|
+
title?: string
|
|
64
|
+
inline?: boolean
|
|
65
|
+
palette?: boolean
|
|
66
|
+
singleSelection?: boolean
|
|
67
|
+
elementSize?: 'auto' | 'small' | 'medium' | 'large'
|
|
68
|
+
variant?: 'plain' | 'done' | 'prominent'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface StackToolbarMenuActionProps extends StackToolbarItemProps {
|
|
72
|
+
image?: ImageSourcePropType
|
|
73
|
+
destructive?: boolean
|
|
74
|
+
discoverabilityLabel?: string
|
|
75
|
+
subtitle?: string
|
|
76
|
+
isOn?: boolean
|
|
77
|
+
onPress?: () => void
|
|
78
|
+
unstable_keepPresented?: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface StackToolbarSpacerProps {
|
|
82
|
+
width?: number
|
|
83
|
+
hidden?: boolean
|
|
84
|
+
sharesBackground?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface StackToolbarSearchBarSlotProps {
|
|
88
|
+
hidden?: boolean
|
|
89
|
+
hidesSharedBackground?: boolean
|
|
90
|
+
separateBackground?: boolean
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface StackToolbarViewProps {
|
|
94
|
+
children?: ReactNode
|
|
95
|
+
hidden?: boolean
|
|
96
|
+
hidesSharedBackground?: boolean
|
|
97
|
+
separateBackground?: boolean
|
|
98
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import TestRenderer, { act } from 'react-test-renderer'
|
|
3
|
+
import { Platform } from 'react-native'
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
const setOptions = vi.fn()
|
|
7
|
+
|
|
8
|
+
vi.mock('../../../router/useNavigation', () => ({
|
|
9
|
+
useNavigation: () => ({ setOptions }),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
import { StackToolbar } from '../StackToolbar'
|
|
13
|
+
|
|
14
|
+
describe('StackToolbar runtime options', () => {
|
|
15
|
+
const originalOS = Platform.OS
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
;(Platform as any).OS = originalOS
|
|
19
|
+
setOptions.mockClear()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('does not write undefined header overrides when the toolbar unmounts', () => {
|
|
23
|
+
;(Platform as any).OS = 'ios'
|
|
24
|
+
let renderer: TestRenderer.ReactTestRenderer
|
|
25
|
+
|
|
26
|
+
act(() => {
|
|
27
|
+
renderer = TestRenderer.create(
|
|
28
|
+
<StackToolbar placement="right">
|
|
29
|
+
<StackToolbar.Button icon="bell" />
|
|
30
|
+
</StackToolbar>
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
expect(setOptions).toHaveBeenCalledTimes(1)
|
|
35
|
+
expect(setOptions).toHaveBeenLastCalledWith(
|
|
36
|
+
expect.objectContaining({
|
|
37
|
+
headerShown: true,
|
|
38
|
+
unstable_headerRightItems: expect.any(Function),
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
act(() => renderer!.unmount())
|
|
43
|
+
|
|
44
|
+
expect(setOptions).toHaveBeenCalledTimes(1)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '../StackHeaderComponent'
|
|
26
26
|
import { appendScreenStackPropsToOptions } from '../StackScreen'
|
|
27
27
|
import { StackHeader } from '../index'
|
|
28
|
+
import { StackToolbar, appendStackToolbarPropsToOptions } from '../StackToolbar'
|
|
28
29
|
|
|
29
30
|
describe('Stack Header Composition', () => {
|
|
30
31
|
describe('StackHeaderTitle', () => {
|
|
@@ -640,4 +641,179 @@ describe('Stack Header Composition', () => {
|
|
|
640
641
|
})
|
|
641
642
|
})
|
|
642
643
|
})
|
|
644
|
+
|
|
645
|
+
describe('StackToolbar', () => {
|
|
646
|
+
const withIOS = <T,>(run: () => T): T => {
|
|
647
|
+
const originalOS = Platform.OS
|
|
648
|
+
;(Platform as any).OS = 'ios'
|
|
649
|
+
try {
|
|
650
|
+
return run()
|
|
651
|
+
} finally {
|
|
652
|
+
;(Platform as any).OS = originalOS
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
it('converts buttons, SF Symbols, badges, and iOS 26 background props to native header items', () =>
|
|
657
|
+
withIOS(() => {
|
|
658
|
+
const onPress = () => {}
|
|
659
|
+
const result = appendStackToolbarPropsToOptions(
|
|
660
|
+
{ title: 'Inbox' },
|
|
661
|
+
{
|
|
662
|
+
placement: 'right',
|
|
663
|
+
children: (
|
|
664
|
+
<StackToolbar.Button
|
|
665
|
+
onPress={onPress}
|
|
666
|
+
selected
|
|
667
|
+
separateBackground
|
|
668
|
+
hidesSharedBackground
|
|
669
|
+
>
|
|
670
|
+
<StackToolbar.Icon sf="bell" />
|
|
671
|
+
<StackToolbar.Label>Notifications</StackToolbar.Label>
|
|
672
|
+
<StackToolbar.Badge style={{ color: 'white', backgroundColor: 'red' }}>
|
|
673
|
+
5
|
|
674
|
+
</StackToolbar.Badge>
|
|
675
|
+
</StackToolbar.Button>
|
|
676
|
+
),
|
|
677
|
+
}
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
expect(result.title).toBe('Inbox')
|
|
681
|
+
expect(result.headerShown).toBe(true)
|
|
682
|
+
const items = result.unstable_headerRightItems?.({} as any)
|
|
683
|
+
expect(items).toEqual([
|
|
684
|
+
expect.objectContaining({
|
|
685
|
+
type: 'button',
|
|
686
|
+
label: 'Notifications',
|
|
687
|
+
icon: { type: 'sfSymbol', name: 'bell' },
|
|
688
|
+
badge: {
|
|
689
|
+
value: '5',
|
|
690
|
+
style: expect.objectContaining({ color: 'white', backgroundColor: 'red' }),
|
|
691
|
+
},
|
|
692
|
+
selected: true,
|
|
693
|
+
sharesBackground: false,
|
|
694
|
+
hidesSharedBackground: true,
|
|
695
|
+
onPress,
|
|
696
|
+
}),
|
|
697
|
+
])
|
|
698
|
+
}))
|
|
699
|
+
|
|
700
|
+
it('uses original image colors unless a tint or rendering mode requests a template', () =>
|
|
701
|
+
withIOS(() => {
|
|
702
|
+
const source = { uri: 'https://example.com/icon.png' }
|
|
703
|
+
const result = appendStackToolbarPropsToOptions(
|
|
704
|
+
{},
|
|
705
|
+
{
|
|
706
|
+
placement: 'right',
|
|
707
|
+
children: [
|
|
708
|
+
<StackToolbar.Button key="original" icon={source} />,
|
|
709
|
+
<StackToolbar.Button key="tinted" icon={source} tintColor="blue" />,
|
|
710
|
+
<StackToolbar.Button key="explicit" tintColor="blue">
|
|
711
|
+
<StackToolbar.Icon src={source} renderingMode="original" />
|
|
712
|
+
</StackToolbar.Button>,
|
|
713
|
+
],
|
|
714
|
+
}
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
expect(result.unstable_headerRightItems?.({} as any)).toMatchObject([
|
|
718
|
+
{ icon: { type: 'image', source, tinted: false } },
|
|
719
|
+
{ icon: { type: 'image', source, tinted: true } },
|
|
720
|
+
{ icon: { type: 'image', source, tinted: false } },
|
|
721
|
+
])
|
|
722
|
+
}))
|
|
723
|
+
|
|
724
|
+
it('converts nested native menus and action state', () =>
|
|
725
|
+
withIOS(() => {
|
|
726
|
+
const archive = () => {}
|
|
727
|
+
const result = appendStackToolbarPropsToOptions(
|
|
728
|
+
{},
|
|
729
|
+
{
|
|
730
|
+
placement: 'left',
|
|
731
|
+
children: (
|
|
732
|
+
<StackToolbar.Menu title="Actions" icon="ellipsis.circle">
|
|
733
|
+
<StackToolbar.MenuAction icon="archivebox" isOn onPress={archive}>
|
|
734
|
+
Archive
|
|
735
|
+
</StackToolbar.MenuAction>
|
|
736
|
+
<StackToolbar.Menu destructive inline title="Move to">
|
|
737
|
+
<StackToolbar.MenuAction destructive>Trash</StackToolbar.MenuAction>
|
|
738
|
+
</StackToolbar.Menu>
|
|
739
|
+
</StackToolbar.Menu>
|
|
740
|
+
),
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
const items = result.unstable_headerLeftItems?.({} as any)
|
|
745
|
+
expect(items).toHaveLength(1)
|
|
746
|
+
expect(items?.[0]).toMatchObject({
|
|
747
|
+
type: 'menu',
|
|
748
|
+
icon: { type: 'sfSymbol', name: 'ellipsis.circle' },
|
|
749
|
+
menu: {
|
|
750
|
+
title: 'Actions',
|
|
751
|
+
items: [
|
|
752
|
+
{
|
|
753
|
+
type: 'action',
|
|
754
|
+
label: 'Archive',
|
|
755
|
+
icon: { type: 'sfSymbol', name: 'archivebox' },
|
|
756
|
+
state: 'on',
|
|
757
|
+
onPress: archive,
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
type: 'submenu',
|
|
761
|
+
label: 'Move to',
|
|
762
|
+
inline: true,
|
|
763
|
+
destructive: true,
|
|
764
|
+
items: [{ type: 'action', label: 'Trash', destructive: true }],
|
|
765
|
+
},
|
|
766
|
+
],
|
|
767
|
+
},
|
|
768
|
+
})
|
|
769
|
+
}))
|
|
770
|
+
|
|
771
|
+
it('merges left and right toolbars declared on Stack.Screen', () =>
|
|
772
|
+
withIOS(() => {
|
|
773
|
+
const result = appendScreenStackPropsToOptions(
|
|
774
|
+
{ title: 'Document' },
|
|
775
|
+
{
|
|
776
|
+
children: [
|
|
777
|
+
<StackToolbar key="left" placement="left">
|
|
778
|
+
<StackToolbar.Button icon="sidebar.left" />
|
|
779
|
+
</StackToolbar>,
|
|
780
|
+
<StackToolbar key="right" placement="right">
|
|
781
|
+
<StackToolbar.Button icon="square.and.arrow.up" />
|
|
782
|
+
</StackToolbar>,
|
|
783
|
+
],
|
|
784
|
+
}
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
expect(result.title).toBe('Document')
|
|
788
|
+
expect(result.unstable_headerLeftItems?.({} as any)).toHaveLength(1)
|
|
789
|
+
expect(result.unstable_headerRightItems?.({} as any)).toHaveLength(1)
|
|
790
|
+
}))
|
|
791
|
+
|
|
792
|
+
it('uses custom header content with asChild', () =>
|
|
793
|
+
withIOS(() => {
|
|
794
|
+
const custom = <button>Custom</button>
|
|
795
|
+
const result = appendStackToolbarPropsToOptions(
|
|
796
|
+
{},
|
|
797
|
+
{ placement: 'right', asChild: true, children: custom }
|
|
798
|
+
)
|
|
799
|
+
expect(result.headerRight?.({} as any)).toBe(custom)
|
|
800
|
+
expect(result.unstable_headerRightItems).toBeUndefined()
|
|
801
|
+
}))
|
|
802
|
+
|
|
803
|
+
it('does not install native toolbar options on web', () => {
|
|
804
|
+
const originalOS = Platform.OS
|
|
805
|
+
;(Platform as any).OS = 'web'
|
|
806
|
+
try {
|
|
807
|
+
const original = { title: 'Web' }
|
|
808
|
+
expect(
|
|
809
|
+
appendStackToolbarPropsToOptions(original, {
|
|
810
|
+
placement: 'right',
|
|
811
|
+
children: <StackToolbar.Button icon="star" />,
|
|
812
|
+
})
|
|
813
|
+
).toBe(original)
|
|
814
|
+
} finally {
|
|
815
|
+
;(Platform as any).OS = originalOS
|
|
816
|
+
}
|
|
817
|
+
})
|
|
818
|
+
})
|
|
643
819
|
})
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type StackHeaderSearchBarProps,
|
|
11
11
|
} from './StackHeaderSearchBar'
|
|
12
12
|
import { StackHeaderTitle, type StackHeaderTitleProps } from './StackHeaderTitle'
|
|
13
|
+
import { StackToolbar } from './StackToolbar'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Compound component for configuring stack headers.
|
|
@@ -36,8 +37,23 @@ export {
|
|
|
36
37
|
type StackHeaderSearchBarProps,
|
|
37
38
|
StackHeaderTitle,
|
|
38
39
|
type StackHeaderTitleProps,
|
|
40
|
+
StackToolbar,
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
export type {
|
|
44
|
+
StackToolbarBadgeProps,
|
|
45
|
+
StackToolbarButtonProps,
|
|
46
|
+
StackToolbarIconProps,
|
|
47
|
+
StackToolbarLabelProps,
|
|
48
|
+
StackToolbarMenuActionProps,
|
|
49
|
+
StackToolbarMenuProps,
|
|
50
|
+
StackToolbarPlacement,
|
|
51
|
+
StackToolbarProps,
|
|
52
|
+
StackToolbarSearchBarSlotProps,
|
|
53
|
+
StackToolbarSpacerProps,
|
|
54
|
+
StackToolbarViewProps,
|
|
55
|
+
} from './StackToolbar'
|
|
56
|
+
|
|
41
57
|
export {
|
|
42
58
|
StackScreen,
|
|
43
59
|
appendScreenStackPropsToOptions,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import TestRenderer, { act } from 'react-test-renderer'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { Protected } from '../views/Protected'
|
|
5
|
+
import { Screen } from '../views/Screen'
|
|
6
|
+
import { useFilterScreenChildren } from './withLayoutContext'
|
|
7
|
+
|
|
8
|
+
function readGuardedRedirects(children: React.ReactNode) {
|
|
9
|
+
let result: ReturnType<typeof useFilterScreenChildren> | undefined
|
|
10
|
+
|
|
11
|
+
function Capture() {
|
|
12
|
+
result = useFilterScreenChildren(children)
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
act(() => {
|
|
17
|
+
TestRenderer.create(<Capture />)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return result!.guardedRedirects
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('Protected redirectTo', () => {
|
|
24
|
+
it('associates a failed guard redirect with each protected screen', () => {
|
|
25
|
+
const redirects = readGuardedRedirects(
|
|
26
|
+
<Protected guard={false} redirectTo="/login">
|
|
27
|
+
<Screen name="dashboard" />
|
|
28
|
+
<Screen name="settings" />
|
|
29
|
+
</Protected>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
expect(Array.from(redirects)).toEqual([
|
|
33
|
+
['dashboard', '/login'],
|
|
34
|
+
['settings', '/login'],
|
|
35
|
+
])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('uses the innermost failing guard redirect', () => {
|
|
39
|
+
const redirects = readGuardedRedirects(
|
|
40
|
+
<Protected guard={false} redirectTo="/login">
|
|
41
|
+
<Protected guard={false} redirectTo="/subscribe">
|
|
42
|
+
<Screen name="premium" />
|
|
43
|
+
</Protected>
|
|
44
|
+
</Protected>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
expect(redirects.get('premium')).toBe('/subscribe')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('inherits a failed parent redirect through a passing child guard', () => {
|
|
51
|
+
const redirects = readGuardedRedirects(
|
|
52
|
+
<Protected guard={false} redirectTo="/login">
|
|
53
|
+
<Protected guard>
|
|
54
|
+
<Screen name="account" />
|
|
55
|
+
</Protected>
|
|
56
|
+
</Protected>
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
expect(redirects.get('account')).toBe('/login')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('ignores redirectTo on a passing parent guard', () => {
|
|
63
|
+
const redirects = readGuardedRedirects(
|
|
64
|
+
<Protected guard redirectTo="/login">
|
|
65
|
+
<Protected guard={false}>
|
|
66
|
+
<Screen name="account" />
|
|
67
|
+
</Protected>
|
|
68
|
+
</Protected>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
expect(redirects.has('account')).toBe(true)
|
|
72
|
+
expect(redirects.get('account')).toBeUndefined()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { EventMapBase, NavigationState } from '@react-navigation/native'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import type { OneRouter } from '../interfaces/router'
|
|
4
|
+
import { getContextKey, stripInvisibleSegmentsFromPath } from '../router/matchers'
|
|
5
|
+
import { useContextKey, useRouteNode } from '../router/Route'
|
|
6
|
+
import {
|
|
7
|
+
registerProtectedRoutes,
|
|
8
|
+
replace,
|
|
9
|
+
resolveProtectedHref,
|
|
10
|
+
routeInfo,
|
|
11
|
+
unregisterProtectedRoutes,
|
|
12
|
+
} from '../router/router'
|
|
5
13
|
import { type ScreenProps, useSortedScreens } from '../router/useScreens'
|
|
14
|
+
import { sortRoutesWithInitial } from '../router/sortRoutes'
|
|
6
15
|
import type { PickPartial } from '../types'
|
|
7
16
|
import { withStaticProperties } from '../utils/withStaticProperties'
|
|
8
17
|
import { isProtectedElement } from '../views/Protected'
|
|
@@ -24,12 +33,17 @@ export function useFilterScreenChildren(
|
|
|
24
33
|
const customChildren: any[] = []
|
|
25
34
|
const screens: any[] = []
|
|
26
35
|
const protectedScreens = new Set<string>()
|
|
36
|
+
const guardedRedirects = new Map<string, OneRouter.Href | undefined>()
|
|
27
37
|
|
|
28
38
|
/**
|
|
29
39
|
* Recursively process children, handling Protected elements.
|
|
30
40
|
* When exclude is true, all Screen children are added to protectedScreens instead of screens.
|
|
31
41
|
*/
|
|
32
|
-
function flattenChild(
|
|
42
|
+
function flattenChild(
|
|
43
|
+
child: React.ReactNode,
|
|
44
|
+
exclude = false,
|
|
45
|
+
redirectTo?: OneRouter.Href
|
|
46
|
+
) {
|
|
33
47
|
// Handle Screen or StackScreen elements
|
|
34
48
|
if (
|
|
35
49
|
React.isValidElement(child) &&
|
|
@@ -61,6 +75,7 @@ export function useFilterScreenChildren(
|
|
|
61
75
|
const screenProps = child.props as ScreenProps
|
|
62
76
|
if (exclude && screenProps.name) {
|
|
63
77
|
protectedScreens.add(screenProps.name)
|
|
78
|
+
guardedRedirects.set(screenProps.name, redirectTo)
|
|
64
79
|
} else {
|
|
65
80
|
screens.push(screenProps)
|
|
66
81
|
}
|
|
@@ -69,10 +84,11 @@ export function useFilterScreenChildren(
|
|
|
69
84
|
|
|
70
85
|
// Handle Protected elements - recursively process children with guard logic
|
|
71
86
|
if (isProtectedElement(child)) {
|
|
72
|
-
|
|
73
|
-
const excludeChildren = exclude ||
|
|
87
|
+
const guardFails = !child.props.guard
|
|
88
|
+
const excludeChildren = exclude || guardFails
|
|
89
|
+
const childRedirectTo = guardFails ? child.props.redirectTo : redirectTo
|
|
74
90
|
React.Children.forEach(child.props.children, (nested) => {
|
|
75
|
-
flattenChild(nested, excludeChildren)
|
|
91
|
+
flattenChild(nested, excludeChildren, childRedirectTo)
|
|
76
92
|
})
|
|
77
93
|
return
|
|
78
94
|
}
|
|
@@ -102,10 +118,40 @@ export function useFilterScreenChildren(
|
|
|
102
118
|
screens,
|
|
103
119
|
children: customChildren,
|
|
104
120
|
protectedScreens,
|
|
121
|
+
guardedRedirects,
|
|
105
122
|
}
|
|
106
123
|
}, [children, contextKey, isCustomNavigator])
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
export function useResolvedGuardedRedirects(
|
|
127
|
+
guardedRedirects: Map<string, OneRouter.Href | undefined>
|
|
128
|
+
) {
|
|
129
|
+
const node = useRouteNode()
|
|
130
|
+
|
|
131
|
+
return React.useMemo(() => {
|
|
132
|
+
if (!node) {
|
|
133
|
+
return guardedRedirects
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const defaultRoute = [...node.children]
|
|
137
|
+
.sort(sortRoutesWithInitial(node.initialRouteName))
|
|
138
|
+
.find((child) => {
|
|
139
|
+
const normalized = child.route.replace(/\/index$/, '')
|
|
140
|
+
return !(guardedRedirects.has(child.route) || guardedRedirects.has(normalized))
|
|
141
|
+
})
|
|
142
|
+
const defaultHref = defaultRoute
|
|
143
|
+
? stripInvisibleSegmentsFromPath(getContextKey(defaultRoute.contextKey)) || '/'
|
|
144
|
+
: undefined
|
|
145
|
+
|
|
146
|
+
return new Map(
|
|
147
|
+
Array.from(guardedRedirects, ([name, redirectTo]) => [
|
|
148
|
+
name,
|
|
149
|
+
redirectTo ?? defaultHref,
|
|
150
|
+
])
|
|
151
|
+
)
|
|
152
|
+
}, [guardedRedirects, node])
|
|
153
|
+
}
|
|
154
|
+
|
|
109
155
|
/** Return a navigator that automatically injects matched routes and renders nothing when there are no children. Return type with children prop optional */
|
|
110
156
|
export function withLayoutContext<
|
|
111
157
|
TOptions extends object,
|
|
@@ -124,20 +170,33 @@ export function withLayoutContext<
|
|
|
124
170
|
(propsIn, ref) => {
|
|
125
171
|
const { children, ...props } = propsIn as React.ComponentProps<T>
|
|
126
172
|
const contextKey = useContextKey()
|
|
127
|
-
const { screens, protectedScreens } = useFilterScreenChildren(
|
|
128
|
-
|
|
129
|
-
|
|
173
|
+
const { screens, protectedScreens, guardedRedirects } = useFilterScreenChildren(
|
|
174
|
+
children,
|
|
175
|
+
{ contextKey }
|
|
176
|
+
)
|
|
177
|
+
const resolvedGuardedRedirects = useResolvedGuardedRedirects(guardedRedirects)
|
|
130
178
|
|
|
131
179
|
// Register protected routes globally so linkTo can block navigation to them
|
|
132
180
|
// Register immediately (not just in effect) to catch navigation attempts during first render
|
|
133
|
-
registerProtectedRoutes(contextKey,
|
|
181
|
+
registerProtectedRoutes(contextKey, resolvedGuardedRedirects)
|
|
182
|
+
|
|
183
|
+
const currentPathname = routeInfo?.pathname
|
|
184
|
+
const protectedHref = currentPathname
|
|
185
|
+
? resolveProtectedHref(currentPathname)
|
|
186
|
+
: currentPathname
|
|
134
187
|
|
|
135
188
|
React.useEffect(() => {
|
|
136
|
-
registerProtectedRoutes(contextKey,
|
|
189
|
+
registerProtectedRoutes(contextKey, resolvedGuardedRedirects)
|
|
137
190
|
return () => {
|
|
138
191
|
unregisterProtectedRoutes(contextKey)
|
|
139
192
|
}
|
|
140
|
-
}, [contextKey,
|
|
193
|
+
}, [contextKey, resolvedGuardedRedirects])
|
|
194
|
+
|
|
195
|
+
React.useEffect(() => {
|
|
196
|
+
if (currentPathname && protectedHref && protectedHref !== currentPathname) {
|
|
197
|
+
replace(protectedHref)
|
|
198
|
+
}
|
|
199
|
+
}, [currentPathname, protectedHref])
|
|
141
200
|
|
|
142
201
|
const processed = processor ? processor(screens ?? ([] as any)) : screens
|
|
143
202
|
const sorted = useSortedScreens((processed ?? []) as any, {
|
package/src/router/Route.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { createContext, type ReactNode, useContext } from 'react'
|
|
2
2
|
import { findFocusedRoute } from '../fork/findFocusedRoute'
|
|
3
3
|
import type { ErrorBoundaryProps } from '../views/Try'
|
|
4
|
+
import type { SuspenseFallbackProps } from '../views/SuspenseFallback'
|
|
4
5
|
import type { LoaderProps } from '../types'
|
|
5
6
|
import type { One } from '../vite/types'
|
|
6
7
|
import type { ParamValidator, RouteValidationFn } from '../validateParams'
|
|
@@ -37,6 +38,7 @@ export type SlotConfig = {
|
|
|
37
38
|
|
|
38
39
|
export type LoadedRoute = {
|
|
39
40
|
ErrorBoundary?: React.ComponentType<ErrorBoundaryProps>
|
|
41
|
+
SuspenseFallback?: React.ComponentType<SuspenseFallbackProps>
|
|
40
42
|
default?: React.ComponentType<any>
|
|
41
43
|
unstable_settings?: Record<string, any>
|
|
42
44
|
getNavOptions?: (args: any) => any
|
|
@@ -124,8 +126,14 @@ export const RouteParamsContext = createContext<
|
|
|
124
126
|
|
|
125
127
|
const CurrentRouteContext = React.createContext<RouteNode | null>(null)
|
|
126
128
|
|
|
129
|
+
/** Allows a layout to provide a Suspense fallback for its child routes. */
|
|
130
|
+
export const SuspenseFallbackContext = createContext<
|
|
131
|
+
React.ComponentType<SuspenseFallbackProps> | undefined
|
|
132
|
+
>(undefined)
|
|
133
|
+
|
|
127
134
|
if (process.env.NODE_ENV !== 'production') {
|
|
128
135
|
CurrentRouteContext.displayName = 'RouteNode'
|
|
136
|
+
SuspenseFallbackContext.displayName = 'SuspenseFallback'
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
/** Return the RouteNode at the current contextual boundary. */
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
isRouteProtected,
|
|
4
|
+
registerProtectedRoutes,
|
|
5
|
+
resolveProtectedHref,
|
|
6
|
+
unregisterProtectedRoutes,
|
|
7
|
+
} from './router'
|
|
8
|
+
|
|
9
|
+
const contextKey = '/account'
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
unregisterProtectedRoutes(contextKey)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('protected route redirects', () => {
|
|
16
|
+
it('resolves a protected href to its redirect target', () => {
|
|
17
|
+
registerProtectedRoutes(contextKey, new Map([['settings', '/login']]))
|
|
18
|
+
|
|
19
|
+
expect(isRouteProtected('/account/settings?tab=billing')).toBe(true)
|
|
20
|
+
expect(resolveProtectedHref('/account/settings?tab=billing')).toBe('/login')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('blocks a protected href without an explicit target', () => {
|
|
24
|
+
registerProtectedRoutes(contextKey, new Map([['settings', undefined]]))
|
|
25
|
+
|
|
26
|
+
expect(resolveProtectedHref('/account/settings')).toBeUndefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('leaves allowed and similarly prefixed routes unchanged', () => {
|
|
30
|
+
registerProtectedRoutes(contextKey, new Map([['settings', '/login']]))
|
|
31
|
+
|
|
32
|
+
expect(resolveProtectedHref('/account/profile')).toBe('/account/profile')
|
|
33
|
+
expect(resolveProtectedHref('/accounting/settings')).toBe('/accounting/settings')
|
|
34
|
+
})
|
|
35
|
+
})
|