@faststore/core 4.8.1-dev.1 → 4.8.1-dev.2

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 (37) hide show
  1. package/.turbo/turbo-generate.log +3 -3
  2. package/.turbo/turbo-test.log +126 -123
  3. package/@generated/gql.ts +3 -3
  4. package/@generated/graphql.ts +4 -2
  5. package/@generated/persisted-documents.json +1 -1
  6. package/@generated/schema.graphql +4 -0
  7. package/CHANGELOG.md +6 -0
  8. package/package.json +6 -6
  9. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/ContractSwitcher.tsx +4 -6
  10. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +18 -3
  11. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader.tsx +1 -0
  12. package/src/components/account/Drawer/OrganizationDrawer/section.module.scss +7 -0
  13. package/src/components/account/Drawer/OrganizationSignInButton/OrganizationSignInButton.tsx +7 -9
  14. package/src/components/account/Menu/Menu.tsx +24 -7
  15. package/src/components/navigation/Navbar/Navbar.tsx +17 -19
  16. package/src/components/navigation/Navbar/NavbarAccountArea.tsx +57 -0
  17. package/src/components/navigation/NavbarSlider/NavbarSlider.tsx +16 -18
  18. package/src/pages/api/fs/switch-contract.ts +194 -0
  19. package/src/sdk/account/changeContractToken.ts +19 -63
  20. package/src/sdk/account/clearPersistedSessionState.ts +13 -7
  21. package/src/sdk/account/useAvailableContracts.ts +1 -0
  22. package/src/sdk/account/useSwitchContract.ts +11 -3
  23. package/src/sdk/session/index.ts +2 -1
  24. package/src/sdk/session/isSignInAreaResolved.ts +12 -0
  25. package/test/components/account/ContractSwitcher.browser.test.tsx +42 -0
  26. package/test/components/account/Menu.browser.test.tsx +123 -0
  27. package/test/components/account/OrganizationDrawer.browser.test.tsx +148 -0
  28. package/test/components/account/OrganizationDrawerHeader.browser.test.tsx +2 -0
  29. package/test/components/account/OrganizationSignInButton.browser.test.tsx +49 -0
  30. package/test/components/navigation/Navbar.browser.test.tsx +183 -0
  31. package/test/components/navigation/NavbarAccountArea.browser.test.tsx +115 -0
  32. package/test/components/navigation/NavbarSlider.browser.test.tsx +143 -0
  33. package/test/pages/api/switch-contract.test.ts +385 -0
  34. package/test/sdk/account/changeContractToken.browser.test.ts +23 -71
  35. package/test/sdk/account/clearPersistedSessionState.browser.test.ts +24 -0
  36. package/test/sdk/account/useSwitchContract.browser.test.ts +21 -0
  37. package/test/sdk/session/isSignInAreaResolved.test.ts +22 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import '@testing-library/jest-dom/vitest'
6
+ import { render, screen } from '@testing-library/react'
7
+ import { forwardRef } from 'react'
8
+ import { describe, expect, it, vi } from 'vitest'
9
+
10
+ const mockUseSession = vi.hoisted(() => vi.fn())
11
+ const mockUseScreenResize = vi.hoisted(() => vi.fn())
12
+ const mockNavbarAccountArea = vi.hoisted(() => vi.fn())
13
+ const mockStoreConfig = vi.hoisted(() => ({
14
+ experimental: { enableFaststoreMyAccount: false },
15
+ localization: { enabled: false },
16
+ }))
17
+
18
+ vi.mock('next/dynamic', () => ({
19
+ default: () => () => null,
20
+ }))
21
+
22
+ vi.mock('@faststore/ui', () => ({
23
+ Icon: () => null,
24
+ Skeleton: () => null,
25
+ useScrollDirection: () => 'up',
26
+ useUI: () => ({ openNavbar: vi.fn(), navbar: false }),
27
+ }))
28
+
29
+ vi.mock('discovery.config', () => ({ default: mockStoreConfig }))
30
+
31
+ vi.mock('src/components/account/Drawer/OrganizationSignInButton', () => ({
32
+ OrganizationSignInButton: () => null,
33
+ }))
34
+
35
+ vi.mock('src/components/cart/CartToggle', () => ({
36
+ default: () => <div data-testid="cart-toggle" />,
37
+ }))
38
+
39
+ vi.mock('src/components/search/SearchInput', () => ({
40
+ default: forwardRef(function SearchInputStub(_props, _ref) {
41
+ return <div data-testid="search-input" />
42
+ }),
43
+ }))
44
+
45
+ vi.mock('src/components/ui/Link', () => ({
46
+ default: ({ children }: { children?: React.ReactNode }) => (
47
+ <a href="/">{children}</a>
48
+ ),
49
+ }))
50
+
51
+ vi.mock('src/components/ui/LocalizationButton', () => ({
52
+ default: () => <div data-testid="localization-button" />,
53
+ }))
54
+
55
+ vi.mock('src/components/ui/Logo', () => ({
56
+ default: () => <div data-testid="logo" />,
57
+ }))
58
+
59
+ vi.mock('../../../src/components/navigation/Navbar/NavbarAccountArea', () => ({
60
+ NavbarAccountArea: (props: Record<string, unknown>) => {
61
+ mockNavbarAccountArea(props)
62
+ return <div data-testid="navbar-account-area" />
63
+ },
64
+ }))
65
+
66
+ vi.mock('src/sdk/overrides/OverrideContext', () => ({
67
+ useOverrideComponents: () => ({
68
+ Navbar: {
69
+ Component: ({ children }: React.PropsWithChildren) => (
70
+ <div data-testid="navbar-wrapper">{children}</div>
71
+ ),
72
+ props: {},
73
+ },
74
+ NavbarHeader: {
75
+ Component: ({ children }: React.PropsWithChildren) => (
76
+ <header>{children}</header>
77
+ ),
78
+ props: {},
79
+ },
80
+ NavbarRow: {
81
+ Component: ({ children }: React.PropsWithChildren) => (
82
+ <div>{children}</div>
83
+ ),
84
+ props: {},
85
+ },
86
+ NavbarButtons: {
87
+ Component: ({ children }: React.PropsWithChildren) => (
88
+ <div>{children}</div>
89
+ ),
90
+ props: {},
91
+ },
92
+ IconButton: {
93
+ Component: (props: Record<string, unknown>) => <button {...props} />,
94
+ props: { 'aria-label': 'menu' },
95
+ },
96
+ _experimentalButtonSignIn: { Component: () => null },
97
+ }),
98
+ }))
99
+
100
+ vi.mock('src/sdk/session', () => ({ useSession: mockUseSession }))
101
+
102
+ vi.mock('src/sdk/ui/useScreenResize', () => ({
103
+ default: mockUseScreenResize,
104
+ }))
105
+
106
+ import Navbar from '../../../src/components/navigation/Navbar/Navbar'
107
+
108
+ const baseProps = {
109
+ logo: {
110
+ src: '/logo.png',
111
+ alt: 'Logo',
112
+ link: { url: '/', title: 'Home' },
113
+ },
114
+ searchInput: { sort: 'score_desc' },
115
+ cart: { alt: 'cart', icon: 'ShoppingCart' },
116
+ signIn: {
117
+ button: {
118
+ icon: { alt: 'account', icon: 'User' },
119
+ label: 'Sign in',
120
+ myAccountLabel: 'My Account',
121
+ },
122
+ },
123
+ region: { icon: 'MapPin', label: 'Region', shouldDisplayRegion: false },
124
+ links: [],
125
+ home: { label: 'Home' },
126
+ menu: { icon: { icon: 'List', alt: 'Menu' } },
127
+ }
128
+
129
+ describe('Navbar', () => {
130
+ it('renders the account area (desktop) and forwards isSignInResolved=false before the session validates', () => {
131
+ mockUseScreenResize.mockReturnValue({ isDesktop: true })
132
+ mockUseSession.mockReturnValue({
133
+ b2b: {},
134
+ isSessionReady: false,
135
+ hasValidated: false,
136
+ })
137
+
138
+ render(<Navbar {...baseProps} />)
139
+
140
+ expect(screen.getByTestId('navbar-account-area')).toBeTruthy()
141
+ expect(mockNavbarAccountArea).toHaveBeenCalledWith(
142
+ expect.objectContaining({
143
+ isSignInResolved: false,
144
+ skeletonAttr: 'data-fs-navbar-signin-skeleton',
145
+ })
146
+ )
147
+ })
148
+
149
+ it('forwards isSignInResolved=true and isOrganizationEnabled for a resolved B2B representative session', () => {
150
+ mockStoreConfig.experimental.enableFaststoreMyAccount = true
151
+ mockUseScreenResize.mockReturnValue({ isDesktop: true })
152
+ mockUseSession.mockReturnValue({
153
+ b2b: { isRepresentative: true },
154
+ isSessionReady: true,
155
+ hasValidated: true,
156
+ })
157
+
158
+ render(<Navbar {...baseProps} />)
159
+
160
+ expect(mockNavbarAccountArea).toHaveBeenCalledWith(
161
+ expect.objectContaining({
162
+ isSignInResolved: true,
163
+ isOrganizationEnabled: true,
164
+ isRepresentative: true,
165
+ })
166
+ )
167
+
168
+ mockStoreConfig.experimental.enableFaststoreMyAccount = false
169
+ })
170
+
171
+ it('does not render the account area on mobile', () => {
172
+ mockUseScreenResize.mockReturnValue({ isDesktop: false })
173
+ mockUseSession.mockReturnValue({
174
+ b2b: {},
175
+ isSessionReady: true,
176
+ hasValidated: true,
177
+ })
178
+
179
+ render(<Navbar {...baseProps} />)
180
+
181
+ expect(screen.queryByTestId('navbar-account-area')).toBeNull()
182
+ })
183
+ })
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import '@testing-library/jest-dom/vitest'
6
+ import { render, screen } from '@testing-library/react'
7
+ import { describe, expect, it, vi } from 'vitest'
8
+
9
+ vi.mock(
10
+ '../../../src/components/account/Drawer/OrganizationSignInButton',
11
+ () => ({
12
+ OrganizationSignInButton: ({
13
+ icon,
14
+ isRepresentative,
15
+ }: {
16
+ icon: { alt: string; icon: string }
17
+ isRepresentative?: boolean
18
+ }) => (
19
+ <button
20
+ type="button"
21
+ data-testid="organization-signin"
22
+ data-icon={icon.alt}
23
+ >
24
+ {isRepresentative ? 'Representative' : 'Not representative'}
25
+ </button>
26
+ ),
27
+ })
28
+ )
29
+
30
+ import { NavbarAccountArea } from '../../../src/components/navigation/Navbar/NavbarAccountArea'
31
+
32
+ const signInButton = {
33
+ icon: { alt: 'account', icon: 'User' },
34
+ label: 'Sign in',
35
+ myAccountLabel: 'My Account',
36
+ }
37
+
38
+ const ButtonSignInStub = {
39
+ Component: ({ label }: { label: string }) => (
40
+ <button type="button" data-testid="storefront-signin">
41
+ {label}
42
+ </button>
43
+ ),
44
+ props: undefined,
45
+ }
46
+
47
+ describe('NavbarAccountArea', () => {
48
+ it('renders the loading skeleton with the Navbar attribute while sign-in is not resolved', () => {
49
+ const { container } = render(
50
+ <NavbarAccountArea
51
+ isSignInResolved={false}
52
+ isOrganizationEnabled={false}
53
+ signInButton={signInButton}
54
+ ButtonSignIn={ButtonSignInStub}
55
+ skeletonAttr="data-fs-navbar-signin-skeleton"
56
+ />
57
+ )
58
+
59
+ expect(
60
+ container.querySelector('[data-fs-navbar-signin-skeleton]')
61
+ ).toBeTruthy()
62
+ expect(
63
+ container.querySelector('[data-fs-navbar-slider-signin-skeleton]')
64
+ ).toBeNull()
65
+ })
66
+
67
+ it('renders the loading skeleton with the NavbarSlider attribute while sign-in is not resolved', () => {
68
+ const { container } = render(
69
+ <NavbarAccountArea
70
+ isSignInResolved={false}
71
+ isOrganizationEnabled={false}
72
+ signInButton={signInButton}
73
+ ButtonSignIn={ButtonSignInStub}
74
+ skeletonAttr="data-fs-navbar-slider-signin-skeleton"
75
+ />
76
+ )
77
+
78
+ expect(
79
+ container.querySelector('[data-fs-navbar-slider-signin-skeleton]')
80
+ ).toBeTruthy()
81
+ })
82
+
83
+ it('renders the OrganizationSignInButton when the organization area is enabled', () => {
84
+ render(
85
+ <NavbarAccountArea
86
+ isSignInResolved
87
+ isOrganizationEnabled
88
+ isRepresentative
89
+ signInButton={signInButton}
90
+ ButtonSignIn={ButtonSignInStub}
91
+ skeletonAttr="data-fs-navbar-signin-skeleton"
92
+ />
93
+ )
94
+
95
+ expect(screen.getByTestId('organization-signin')).toHaveTextContent(
96
+ 'Representative'
97
+ )
98
+ expect(screen.queryByTestId('storefront-signin')).toBeNull()
99
+ })
100
+
101
+ it('renders the storefront ButtonSignIn override when the organization area is not enabled', () => {
102
+ render(
103
+ <NavbarAccountArea
104
+ isSignInResolved
105
+ isOrganizationEnabled={false}
106
+ signInButton={signInButton}
107
+ ButtonSignIn={ButtonSignInStub}
108
+ skeletonAttr="data-fs-navbar-signin-skeleton"
109
+ />
110
+ )
111
+
112
+ expect(screen.getByTestId('storefront-signin')).toHaveTextContent('Sign in')
113
+ expect(screen.queryByTestId('organization-signin')).toBeNull()
114
+ })
115
+ })
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import '@testing-library/jest-dom/vitest'
6
+ import { render, screen } from '@testing-library/react'
7
+ import { describe, expect, it, vi } from 'vitest'
8
+
9
+ const mockUseSession = vi.hoisted(() => vi.fn())
10
+ const mockNavbarAccountArea = vi.hoisted(() => vi.fn())
11
+ const mockStoreConfig = vi.hoisted(() => ({
12
+ experimental: { enableFaststoreMyAccount: false },
13
+ localization: { enabled: false },
14
+ }))
15
+
16
+ vi.mock('@faststore/ui', () => ({
17
+ useFadeEffect: () => ({ fade: 'in', fadeOut: vi.fn() }),
18
+ useUI: () => ({ closeNavbar: vi.fn() }),
19
+ }))
20
+
21
+ vi.mock('discovery.config', () => ({ default: mockStoreConfig }))
22
+
23
+ vi.mock('src/components/navigation/NavbarLinks', () => ({
24
+ default: () => <nav data-testid="navbar-links" />,
25
+ }))
26
+
27
+ vi.mock('src/components/ui/Button', () => ({
28
+ ButtonSignInFallback: () => null,
29
+ }))
30
+
31
+ vi.mock('src/components/ui/Link', () => ({
32
+ default: ({ children }: { children?: React.ReactNode }) => (
33
+ <a href="/">{children}</a>
34
+ ),
35
+ }))
36
+
37
+ vi.mock('src/components/ui/LocalizationButton', () => ({
38
+ default: () => <div data-testid="localization-button" />,
39
+ }))
40
+
41
+ vi.mock('src/components/ui/Logo', () => ({
42
+ default: () => <div data-testid="logo" />,
43
+ }))
44
+
45
+ vi.mock('../../../src/components/navigation/Navbar/NavbarAccountArea', () => ({
46
+ NavbarAccountArea: (props: Record<string, unknown>) => {
47
+ mockNavbarAccountArea(props)
48
+ return <div data-testid="navbar-account-area" />
49
+ },
50
+ }))
51
+
52
+ vi.mock('src/sdk/overrides/OverrideContext', () => ({
53
+ useOverrideComponents: () => ({
54
+ NavbarSlider: {
55
+ Component: ({ children }: React.PropsWithChildren) => (
56
+ <div data-testid="navbar-slider-wrapper">{children}</div>
57
+ ),
58
+ props: {},
59
+ },
60
+ NavbarSliderHeader: {
61
+ Component: ({ children }: React.PropsWithChildren) => (
62
+ <header>{children}</header>
63
+ ),
64
+ props: {},
65
+ },
66
+ NavbarSliderContent: {
67
+ Component: ({ children }: React.PropsWithChildren) => (
68
+ <div>{children}</div>
69
+ ),
70
+ props: {},
71
+ },
72
+ NavbarSliderFooter: {
73
+ Component: ({ children }: React.PropsWithChildren) => (
74
+ <footer>{children}</footer>
75
+ ),
76
+ props: {},
77
+ },
78
+ _experimentalButtonSignIn: { Component: () => null },
79
+ }),
80
+ }))
81
+
82
+ vi.mock('src/sdk/session', () => ({ useSession: mockUseSession }))
83
+
84
+ import NavbarSlider from '../../../src/components/navigation/NavbarSlider/NavbarSlider'
85
+
86
+ const baseProps = {
87
+ logo: {
88
+ src: '/logo.png',
89
+ alt: 'Logo',
90
+ link: { url: '/', title: 'Home' },
91
+ },
92
+ home: { label: 'Home' },
93
+ links: [],
94
+ region: { icon: 'MapPin', label: 'Region', shouldDisplayRegion: false },
95
+ signIn: {
96
+ button: {
97
+ icon: { alt: 'account', icon: 'User' },
98
+ label: 'Sign in',
99
+ myAccountLabel: 'My Account',
100
+ },
101
+ },
102
+ }
103
+
104
+ describe('NavbarSlider', () => {
105
+ it('resolves the sign-in area and forwards isSignInResolved=false while the session has not validated', () => {
106
+ mockUseSession.mockReturnValue({
107
+ b2b: {},
108
+ isSessionReady: false,
109
+ hasValidated: false,
110
+ })
111
+
112
+ render(<NavbarSlider {...baseProps} />)
113
+
114
+ expect(screen.getByTestId('navbar-account-area')).toBeTruthy()
115
+ expect(mockNavbarAccountArea).toHaveBeenCalledWith(
116
+ expect.objectContaining({
117
+ isSignInResolved: false,
118
+ skeletonAttr: 'data-fs-navbar-slider-signin-skeleton',
119
+ })
120
+ )
121
+ })
122
+
123
+ it('forwards isSignInResolved=true and isOrganizationEnabled once the B2B representative session resolves', () => {
124
+ mockStoreConfig.experimental.enableFaststoreMyAccount = true
125
+ mockUseSession.mockReturnValue({
126
+ b2b: { isRepresentative: true },
127
+ isSessionReady: true,
128
+ hasValidated: true,
129
+ })
130
+
131
+ render(<NavbarSlider {...baseProps} />)
132
+
133
+ expect(mockNavbarAccountArea).toHaveBeenCalledWith(
134
+ expect.objectContaining({
135
+ isSignInResolved: true,
136
+ isOrganizationEnabled: true,
137
+ isRepresentative: true,
138
+ })
139
+ )
140
+
141
+ mockStoreConfig.experimental.enableFaststoreMyAccount = false
142
+ })
143
+ })