@faststore/core 4.5.0-dev.0 → 4.5.0-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 (61) hide show
  1. package/.turbo/turbo-generate.log +4 -4
  2. package/.turbo/turbo-test.log +135 -63
  3. package/@generated/cached-operations.json +2 -0
  4. package/@generated/gql.ts +18 -0
  5. package/@generated/graphql.ts +68 -0
  6. package/@generated/persisted-documents.json +3 -0
  7. package/@generated/schema.graphql +41 -0
  8. package/CHANGELOG.md +24 -0
  9. package/cms/faststore/components/cms_component__recommendationshelf.jsonc +86 -0
  10. package/cms/faststore/schema.json +89 -0
  11. package/discovery.config.default.js +3 -0
  12. package/discovery.config.js +10 -1
  13. package/discovery.config.local.example.js +62 -0
  14. package/package.json +6 -6
  15. package/public/icons.svg +4 -0
  16. package/src/Layout.tsx +6 -0
  17. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/ContractSwitcher.tsx +376 -0
  18. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/index.ts +2 -0
  19. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +51 -26
  20. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerFooter.tsx +55 -0
  21. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader.tsx +45 -5
  22. package/src/components/account/Drawer/OrganizationDrawer/index.ts +5 -0
  23. package/src/components/account/Drawer/OrganizationDrawer/section.module.scss +452 -54
  24. package/src/components/cms/global/Components.ts +10 -0
  25. package/src/components/sections/ProductGallery/ProductGallery.tsx +1 -1
  26. package/src/components/sections/RecommendationShelf/RecommendationShelf.tsx +223 -0
  27. package/src/components/sections/RecommendationShelf/RecommendationShelf.types.ts +111 -0
  28. package/src/components/sections/RecommendationShelf/index.ts +1 -0
  29. package/src/components/sections/RecommendationShelf/section.module.scss +49 -0
  30. package/src/components/sections/RecommendationShelf/useRecommendationUserId.ts +49 -0
  31. package/src/components/sections/RecommendationShelf/useRecommendations.ts +55 -0
  32. package/src/components/sections/RecommendationShelf/vrn.ts +55 -0
  33. package/src/pages/[slug]/p.tsx +45 -0
  34. package/src/sdk/account/changeContractToken.ts +119 -0
  35. package/src/sdk/account/clearPersistedSessionState.ts +26 -0
  36. package/src/sdk/account/refreshToken.ts +21 -8
  37. package/src/sdk/account/useAvailableContracts.ts +49 -0
  38. package/src/sdk/account/useSwitchContract.ts +60 -0
  39. package/src/sdk/analytics/hooks/useStartRecommendationSession.ts +96 -0
  40. package/src/sdk/analytics/utils.ts +9 -0
  41. package/src/utils/retry.ts +69 -0
  42. package/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx +235 -0
  43. package/test/components/RecommendationShelf/useRecommendationUserId.test.ts +69 -0
  44. package/test/components/RecommendationShelf/useRecommendations.test.ts +52 -0
  45. package/test/components/RecommendationShelf/vrn.test.ts +56 -0
  46. package/test/components/account/ContractSwitcher.browser.test.tsx +236 -0
  47. package/test/components/account/OrganizationDrawer.browser.test.tsx +197 -0
  48. package/test/components/account/OrganizationDrawerFooter.browser.test.tsx +54 -0
  49. package/test/components/account/OrganizationDrawerHeader.browser.test.tsx +55 -0
  50. package/test/components/account/doLogout.browser.test.ts +102 -0
  51. package/test/sdk/account/changeContractToken.browser.test.ts +157 -0
  52. package/test/sdk/account/clearPersistedSessionState.browser.test.ts +41 -0
  53. package/test/sdk/account/clearPersistedSessionState.test.ts +20 -0
  54. package/test/sdk/account/refreshToken.browser.test.ts +34 -0
  55. package/test/sdk/account/refreshToken.test.ts +87 -0
  56. package/test/sdk/account/useAvailableContracts.browser.test.ts +124 -0
  57. package/test/sdk/account/useSwitchContract.browser.test.ts +111 -0
  58. package/test/sdk/analytics/useStartRecommendationSession.test.ts +69 -0
  59. package/test/sdk/analytics/utils.test.ts +23 -0
  60. package/test/server/index.test.ts +3 -0
  61. package/test/utils/retry.test.ts +59 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { fireEvent, render, screen } from '@testing-library/react'
6
+ import type { ReactNode } from 'react'
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
8
+
9
+ const mockFadeOut = vi.hoisted(() => vi.fn())
10
+ const mockUseSession = vi.hoisted(() => vi.fn())
11
+ const mockCloseDrawer = vi.hoisted(() => vi.fn())
12
+
13
+ vi.mock('@faststore/ui', async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import('@faststore/ui')>()
15
+
16
+ return {
17
+ ...actual,
18
+ useFadeEffect: () => ({ fade: 'in', fadeOut: mockFadeOut }),
19
+ SlideOver: ({
20
+ children,
21
+ isOpen,
22
+ onTransitionEnd,
23
+ }: {
24
+ children: ReactNode
25
+ isOpen: boolean
26
+ onTransitionEnd?: () => void
27
+ }) =>
28
+ isOpen ? (
29
+ <div
30
+ data-testid="organization-drawer"
31
+ onTransitionEnd={onTransitionEnd}
32
+ >
33
+ {children}
34
+ </div>
35
+ ) : null,
36
+ }
37
+ })
38
+
39
+ vi.mock('src/sdk/session', () => ({ useSession: mockUseSession }))
40
+
41
+ vi.mock(
42
+ '../../../src/components/account/Drawer/OrganizationDrawer/ContractSwitcher',
43
+ () => ({
44
+ ContractSwitcher: ({
45
+ onBack,
46
+ onClose,
47
+ }: {
48
+ onBack: () => void
49
+ onClose: () => void
50
+ }) => (
51
+ <div data-testid="contract-switcher">
52
+ <button type="button" onClick={onBack}>
53
+ Back to menu
54
+ </button>
55
+ <button type="button" onClick={onClose}>
56
+ Close switcher
57
+ </button>
58
+ </div>
59
+ ),
60
+ })
61
+ )
62
+
63
+ vi.mock(
64
+ '../../../src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerBody',
65
+ () => ({
66
+ OrganizationDrawerBody: () => <nav data-fs-organization-drawer-body />,
67
+ })
68
+ )
69
+
70
+ import { OrganizationDrawer } from '../../../src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer'
71
+
72
+ describe('OrganizationDrawer', () => {
73
+ beforeEach(() => {
74
+ mockUseSession.mockReturnValue({
75
+ b2b: {
76
+ unitId: 'unit-1',
77
+ unitName: 'Stellar Global',
78
+ contractName: 'Acme Contract',
79
+ userName: 'Jane Buyer',
80
+ userEmail: 'jane@example.com',
81
+ organizationManager: true,
82
+ },
83
+ person: { givenName: 'Jane' },
84
+ })
85
+ })
86
+
87
+ afterEach(() => {
88
+ vi.clearAllMocks()
89
+ })
90
+
91
+ it('renders the menu view with contract details and manage footer', () => {
92
+ render(
93
+ <OrganizationDrawer
94
+ isOpen
95
+ closeDrawer={mockCloseDrawer}
96
+ isRepresentative={false}
97
+ />
98
+ )
99
+
100
+ expect(screen.getByText('Acme Contract')).toBeTruthy()
101
+ expect(screen.getByText('Stellar Global')).toBeTruthy()
102
+ expect(screen.getByText('Jane Buyer')).toBeTruthy()
103
+ expect(screen.getByRole('link', { name: /manage/i })).toBeTruthy()
104
+ expect(screen.getByRole('button', { name: /change/i })).toBeTruthy()
105
+ })
106
+
107
+ it('opens the contract switcher and returns to the menu on back', () => {
108
+ render(
109
+ <OrganizationDrawer
110
+ isOpen
111
+ closeDrawer={mockCloseDrawer}
112
+ isRepresentative={false}
113
+ />
114
+ )
115
+
116
+ fireEvent.click(screen.getByRole('button', { name: /change/i }))
117
+ expect(screen.getByTestId('contract-switcher')).toBeTruthy()
118
+
119
+ fireEvent.click(screen.getByRole('button', { name: /back to menu/i }))
120
+ expect(screen.queryByTestId('contract-switcher')).toBeNull()
121
+ expect(screen.getByRole('button', { name: /change/i })).toBeTruthy()
122
+ })
123
+
124
+ it('resets to the menu when the drawer closes', () => {
125
+ const { rerender } = render(
126
+ <OrganizationDrawer
127
+ isOpen
128
+ closeDrawer={mockCloseDrawer}
129
+ isRepresentative={false}
130
+ />
131
+ )
132
+
133
+ fireEvent.click(screen.getByRole('button', { name: /change/i }))
134
+ expect(screen.getByTestId('contract-switcher')).toBeTruthy()
135
+
136
+ rerender(
137
+ <OrganizationDrawer
138
+ isOpen={false}
139
+ closeDrawer={mockCloseDrawer}
140
+ isRepresentative={false}
141
+ />
142
+ )
143
+
144
+ rerender(
145
+ <OrganizationDrawer
146
+ isOpen
147
+ closeDrawer={mockCloseDrawer}
148
+ isRepresentative={false}
149
+ />
150
+ )
151
+
152
+ expect(screen.queryByTestId('contract-switcher')).toBeNull()
153
+ expect(screen.getByRole('button', { name: /change/i })).toBeTruthy()
154
+ })
155
+
156
+ it('falls back to person name when contract metadata is missing', () => {
157
+ mockUseSession.mockReturnValue({
158
+ b2b: {
159
+ unitId: 'unit-1',
160
+ userName: 'Jane Buyer',
161
+ },
162
+ person: { givenName: 'Jane' },
163
+ })
164
+
165
+ render(
166
+ <OrganizationDrawer
167
+ isOpen
168
+ closeDrawer={mockCloseDrawer}
169
+ isRepresentative={false}
170
+ />
171
+ )
172
+
173
+ expect(screen.getByRole('heading', { name: 'Jane', level: 1 })).toBeTruthy()
174
+ })
175
+
176
+ it('hides contract switching when the buyer has no organization unit', () => {
177
+ mockUseSession.mockReturnValue({
178
+ b2b: {
179
+ userName: 'Jane Buyer',
180
+ },
181
+ person: null,
182
+ })
183
+
184
+ render(
185
+ <OrganizationDrawer
186
+ isOpen
187
+ closeDrawer={mockCloseDrawer}
188
+ isRepresentative={false}
189
+ />
190
+ )
191
+
192
+ expect(screen.queryByRole('button', { name: /change/i })).toBeNull()
193
+ expect(
194
+ screen.getByRole('heading', { name: 'Organization', level: 1 })
195
+ ).toBeTruthy()
196
+ })
197
+ })
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { fireEvent, render, screen } from '@testing-library/react'
6
+ import { describe, expect, it, vi } from 'vitest'
7
+
8
+ import { OrganizationDrawerFooter } from '../../../src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerFooter'
9
+
10
+ describe('OrganizationDrawerFooter', () => {
11
+ it('renders org and user details with optional email', () => {
12
+ render(
13
+ <OrganizationDrawerFooter
14
+ orgName="Stellar Global"
15
+ userName="Jane Buyer"
16
+ userEmail="jane@example.com"
17
+ />
18
+ )
19
+
20
+ expect(screen.getByText('Stellar Global')).toBeTruthy()
21
+ expect(screen.getByText('Jane Buyer')).toBeTruthy()
22
+ expect(screen.getByText('jane@example.com')).toBeTruthy()
23
+ })
24
+
25
+ it('shows manage link for organization managers', () => {
26
+ render(
27
+ <OrganizationDrawerFooter
28
+ orgName="Stellar Global"
29
+ userName="Jane Buyer"
30
+ showManageLink
31
+ manageUrl="/pvt/organization-account/org-unit/unit-1"
32
+ />
33
+ )
34
+
35
+ expect(
36
+ screen.getByRole('link', { name: /manage/i }).getAttribute('href')
37
+ ).toBe('/pvt/organization-account/org-unit/unit-1')
38
+ })
39
+
40
+ it('calls onLogoutClick from the logout button', () => {
41
+ const onLogoutClick = vi.fn()
42
+
43
+ render(
44
+ <OrganizationDrawerFooter
45
+ orgName="Stellar Global"
46
+ userName="Jane Buyer"
47
+ onLogoutClick={onLogoutClick}
48
+ />
49
+ )
50
+
51
+ fireEvent.click(screen.getByRole('button', { name: /log out/i }))
52
+ expect(onLogoutClick).toHaveBeenCalledTimes(1)
53
+ })
54
+ })
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { fireEvent, render, screen } from '@testing-library/react'
6
+ import { describe, expect, it, vi } from 'vitest'
7
+
8
+ import { OrganizationDrawerHeader } from '../../../src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader'
9
+
10
+ describe('OrganizationDrawerHeader', () => {
11
+ it('renders contract name fallback initial and links when contractUrl is set', () => {
12
+ render(
13
+ <OrganizationDrawerHeader
14
+ contractName="Acme Corp"
15
+ contractUrl="/pvt/organization-account/org-unit/unit-1"
16
+ onCloseDrawer={vi.fn()}
17
+ />
18
+ )
19
+
20
+ expect(screen.getByText('Acme Corp')).toBeTruthy()
21
+ expect(screen.getByLabelText('Acme Corp').getAttribute('href')).toBe(
22
+ '/pvt/organization-account/org-unit/unit-1'
23
+ )
24
+ expect(screen.getByText('A')).toBeTruthy()
25
+ })
26
+
27
+ it('shows default star and Change action when configured', () => {
28
+ const onChangeContract = vi.fn()
29
+
30
+ render(
31
+ <OrganizationDrawerHeader
32
+ contractName="Acme Corp"
33
+ contractUrl={null}
34
+ isDefault
35
+ onChangeContract={onChangeContract}
36
+ />
37
+ )
38
+
39
+ fireEvent.click(screen.getByRole('button', { name: /change/i }))
40
+ expect(onChangeContract).toHaveBeenCalledTimes(1)
41
+ })
42
+
43
+ it('renders custom contract image and omits manage links without contractUrl', () => {
44
+ render(
45
+ <OrganizationDrawerHeader
46
+ contractName="Acme Corp"
47
+ contractUrl={null}
48
+ contractImage={<img alt="Logo" src="/logo.png" />}
49
+ />
50
+ )
51
+
52
+ expect(screen.getByAltText('Logo')).toBeTruthy()
53
+ expect(screen.queryByRole('link', { name: /acme corp/i })).toBeNull()
54
+ })
55
+ })
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+
7
+ const mockFetch = vi.hoisted(() => vi.fn())
8
+ const mockAssign = vi.hoisted(() => vi.fn())
9
+ const mockDel = vi.hoisted(() => vi.fn())
10
+ const mockSetReloadAfterLogoutReturn = vi.hoisted(() => vi.fn())
11
+ const mockGetStoreURL = vi.hoisted(() => vi.fn())
12
+ const mockExpireCookieClient = vi.hoisted(() => vi.fn())
13
+
14
+ vi.stubGlobal('fetch', mockFetch)
15
+ vi.mock('idb-keyval', () => ({ del: mockDel }))
16
+ vi.mock('src/sdk/localization/useLocalizationConfig', () => ({
17
+ getStoreURL: mockGetStoreURL,
18
+ }))
19
+ vi.mock(
20
+ '../../../src/components/account/Drawer/OrganizationDrawer/useReloadAfterLogoutReturn',
21
+ () => ({
22
+ setReloadAfterLogoutReturn: mockSetReloadAfterLogoutReturn,
23
+ })
24
+ )
25
+ vi.mock('src/utils/clearCookies', () => ({
26
+ expireCookieClient: mockExpireCookieClient,
27
+ getCookieDomains: () => ['.example.com'],
28
+ getCookiePaths: () => ['/'],
29
+ getVtexCookieNames: (names: string[]) =>
30
+ names.filter((name) => /vtex/i.test(name)),
31
+ }))
32
+ vi.mock('../../../discovery.config', () => ({
33
+ default: {
34
+ secureSubdomain: 'https://secure.example.com',
35
+ api: { storeId: 'teststore' },
36
+ },
37
+ }))
38
+
39
+ import { doLogout } from '../../../src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer'
40
+
41
+ describe('doLogout', () => {
42
+ beforeEach(() => {
43
+ mockFetch.mockResolvedValue({
44
+ json: async () => ({}),
45
+ })
46
+ mockGetStoreURL.mockReturnValue('https://store.example.com')
47
+ mockDel.mockResolvedValue(undefined)
48
+
49
+ Object.defineProperty(globalThis, 'indexedDB', {
50
+ configurable: true,
51
+ value: {
52
+ deleteDatabase: () => {
53
+ const request = {
54
+ onsuccess: null as (() => void) | null,
55
+ onerror: null,
56
+ onblocked: null,
57
+ }
58
+
59
+ queueMicrotask(() => request.onsuccess?.())
60
+
61
+ return request
62
+ },
63
+ },
64
+ })
65
+
66
+ Object.defineProperty(globalThis.window, 'location', {
67
+ configurable: true,
68
+ value: {
69
+ hostname: 'store.example.com',
70
+ protocol: 'https:',
71
+ pathname: '/',
72
+ assign: mockAssign,
73
+ },
74
+ })
75
+
76
+ document.cookie = 'VtexIdclientAutCookie_teststore=token; other=1'
77
+ sessionStorage.setItem('faststore_session_ready', '1')
78
+ sessionStorage.setItem('__fs_gallery_page_1', '1')
79
+ })
80
+
81
+ afterEach(() => {
82
+ vi.clearAllMocks()
83
+ sessionStorage.clear()
84
+ document.cookie = ''
85
+ })
86
+
87
+ it('clears client storage, calls logout API, and redirects to VTEX logout', async () => {
88
+ await doLogout()
89
+
90
+ expect(mockDel).toHaveBeenCalledWith('fs::session')
91
+ expect(sessionStorage.getItem('faststore_session_ready')).toBeNull()
92
+ expect(sessionStorage.getItem('__fs_gallery_page_1')).toBeNull()
93
+ expect(mockFetch).toHaveBeenCalledWith('/api/fs/logout', {
94
+ method: 'POST',
95
+ credentials: 'include',
96
+ })
97
+ expect(mockSetReloadAfterLogoutReturn).toHaveBeenCalledTimes(1)
98
+ expect(mockAssign).toHaveBeenCalledWith(
99
+ 'https://secure.example.com/api/vtexid/pub/logout?scope=teststore&returnUrl=https://store.example.com'
100
+ )
101
+ })
102
+ })
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+
7
+ vi.mock('discovery.config', () => ({
8
+ default: {
9
+ api: { storeId: 'b2bfaststoredev' },
10
+ },
11
+ }))
12
+
13
+ const mockFetch = vi.hoisted(() => vi.fn())
14
+ vi.mock('isomorphic-unfetch', () => ({ __esModule: true, default: mockFetch }))
15
+
16
+ import {
17
+ ContractSwitchError,
18
+ applyVtexAuthCookieFromSwitchResponse,
19
+ changeContractToken,
20
+ isContractSwitchEnabled,
21
+ } from '../../../src/sdk/account/changeContractToken'
22
+
23
+ const successPayload = {
24
+ authStatus: 'Success',
25
+ expiresIn: 86_399,
26
+ authCookie: {
27
+ Name: 'VtexIdclientAutCookie_b2bfaststoredev',
28
+ Value: 'new-contract-token',
29
+ },
30
+ accountAuthCookie: null,
31
+ }
32
+
33
+ describe('changeContractToken', () => {
34
+ beforeEach(() => {
35
+ document.cookie = ''
36
+ mockFetch.mockResolvedValue({
37
+ ok: true,
38
+ status: 200,
39
+ json: async () => successPayload,
40
+ })
41
+ })
42
+
43
+ afterEach(() => {
44
+ document.cookie = ''
45
+ vi.clearAllMocks()
46
+ })
47
+
48
+ it('is enabled', () => {
49
+ expect(isContractSwitchEnabled).toBe(true)
50
+ })
51
+
52
+ it('rejects when contractId is missing', async () => {
53
+ await expect(changeContractToken('')).rejects.toThrow(ContractSwitchError)
54
+ })
55
+
56
+ it('posts to switch-properties with the contract customerId', async () => {
57
+ const contractId = '69b5d80e-8f61-44e0-a5cb-db54c326699c'
58
+
59
+ await expect(changeContractToken(contractId)).resolves.toBe(true)
60
+
61
+ expect(mockFetch).toHaveBeenCalledTimes(1)
62
+ const [url, init] = mockFetch.mock.calls[0]
63
+ expect(url).toBe(
64
+ '/api/authenticator/storefront/credential/switch-properties?an=b2bfaststoredev'
65
+ )
66
+ expect(init.method).toBe('POST')
67
+ expect(init.credentials).toBe('include')
68
+ expect(init.signal).toBeDefined()
69
+ expect(JSON.parse(init.body as string)).toEqual({
70
+ properties: { customerId: contractId },
71
+ })
72
+ })
73
+
74
+ it('applies the auth cookie returned by switch-properties', async () => {
75
+ await changeContractToken('contract-1')
76
+
77
+ expect(document.cookie).toContain(
78
+ 'VtexIdclientAutCookie_b2bfaststoredev=new-contract-token'
79
+ )
80
+ })
81
+
82
+ it('throws when the switch request fails', async () => {
83
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 401 })
84
+
85
+ await expect(changeContractToken('contract-1')).rejects.toThrow(
86
+ /Failed to switch contract \(401\)/
87
+ )
88
+ })
89
+
90
+ it('throws when the switch request times out', async () => {
91
+ const timeoutError = new Error('The operation was aborted')
92
+ timeoutError.name = 'TimeoutError'
93
+ mockFetch.mockRejectedValueOnce(timeoutError)
94
+
95
+ await expect(changeContractToken('contract-1')).rejects.toThrow(
96
+ /Contract switch timed out/
97
+ )
98
+ })
99
+
100
+ it('throws when authStatus is not success', async () => {
101
+ mockFetch.mockResolvedValueOnce({
102
+ ok: true,
103
+ status: 200,
104
+ json: async () => ({ ...successPayload, authStatus: 'Failed' }),
105
+ })
106
+
107
+ await expect(changeContractToken('contract-1')).rejects.toThrow(
108
+ /Contract switch was not successful/
109
+ )
110
+ })
111
+
112
+ it('throws when authCookie is missing from a successful response', async () => {
113
+ mockFetch.mockResolvedValueOnce({
114
+ ok: true,
115
+ status: 200,
116
+ json: async () => ({
117
+ authStatus: 'Success',
118
+ authCookie: null,
119
+ }),
120
+ })
121
+
122
+ await expect(changeContractToken('contract-1')).rejects.toThrow(
123
+ /no auth cookie was returned/
124
+ )
125
+ })
126
+ })
127
+
128
+ describe('applyVtexAuthCookieFromSwitchResponse', () => {
129
+ beforeEach(() => {
130
+ document.cookie = ''
131
+ })
132
+
133
+ afterEach(() => {
134
+ document.cookie = ''
135
+ })
136
+
137
+ it('sets auth and account auth cookies when present', () => {
138
+ applyVtexAuthCookieFromSwitchResponse({
139
+ expiresIn: 3600,
140
+ authCookie: {
141
+ Name: 'VtexIdclientAutCookie_b2bfaststoredev',
142
+ Value: 'store-token',
143
+ },
144
+ accountAuthCookie: {
145
+ Name: 'VtexIdclientAutCookie_account',
146
+ Value: 'account-token',
147
+ },
148
+ })
149
+
150
+ expect(document.cookie).toContain(
151
+ 'VtexIdclientAutCookie_b2bfaststoredev=store-token'
152
+ )
153
+ expect(document.cookie).toContain(
154
+ 'VtexIdclientAutCookie_account=account-token'
155
+ )
156
+ })
157
+ })
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+
7
+ const mockDel = vi.hoisted(() => vi.fn())
8
+
9
+ vi.mock('idb-keyval', () => ({
10
+ del: mockDel,
11
+ }))
12
+
13
+ import { SESSION_READY_KEY } from '../../../src/sdk/session/storageKeys'
14
+ import {
15
+ STORAGE_KEY_CACHE_BUST_LAST_VALUE,
16
+ STORAGE_KEY_PERSON_ID,
17
+ } from '../../../src/utils/cookieCacheBusting'
18
+ import { clearPersistedSessionState } from '../../../src/sdk/account/clearPersistedSessionState'
19
+
20
+ describe('clearPersistedSessionState', () => {
21
+ beforeEach(() => {
22
+ mockDel.mockResolvedValue(undefined)
23
+ sessionStorage.setItem(SESSION_READY_KEY, '1')
24
+ sessionStorage.setItem(STORAGE_KEY_PERSON_ID, 'person-1')
25
+ sessionStorage.setItem(STORAGE_KEY_CACHE_BUST_LAST_VALUE, 'abc')
26
+ })
27
+
28
+ afterEach(() => {
29
+ vi.clearAllMocks()
30
+ sessionStorage.clear()
31
+ })
32
+
33
+ it('clears IndexedDB session and sessionStorage keys', async () => {
34
+ await clearPersistedSessionState()
35
+
36
+ expect(mockDel).toHaveBeenCalledWith('fs::session')
37
+ expect(sessionStorage.getItem(SESSION_READY_KEY)).toBeNull()
38
+ expect(sessionStorage.getItem(STORAGE_KEY_PERSON_ID)).toBeNull()
39
+ expect(sessionStorage.getItem(STORAGE_KEY_CACHE_BUST_LAST_VALUE)).toBeNull()
40
+ })
41
+ })
@@ -0,0 +1,20 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const mockDel = vi.hoisted(() => vi.fn())
4
+
5
+ vi.mock('idb-keyval', () => ({ del: mockDel }))
6
+
7
+ import { clearPersistedSessionState } from '../../../src/sdk/account/clearPersistedSessionState'
8
+
9
+ describe('clearPersistedSessionState (node)', () => {
10
+ afterEach(() => {
11
+ vi.clearAllMocks()
12
+ })
13
+
14
+ it('clears IndexedDB session when sessionStorage is unavailable', async () => {
15
+ mockDel.mockResolvedValue(undefined)
16
+
17
+ await expect(clearPersistedSessionState()).resolves.toBeUndefined()
18
+ expect(mockDel).toHaveBeenCalledWith('fs::session')
19
+ })
20
+ })
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+
7
+ const mockFetch = vi.hoisted(() => vi.fn())
8
+
9
+ vi.mock('isomorphic-unfetch', () => ({ __esModule: true, default: mockFetch }))
10
+
11
+ import { refreshTokenRequest } from '../../../src/sdk/account/refreshToken'
12
+
13
+ describe('refreshTokenRequest (browser)', () => {
14
+ beforeEach(() => {
15
+ mockFetch.mockResolvedValue({
16
+ status: 200,
17
+ json: async () => ({ status: 'success' }),
18
+ })
19
+ })
20
+
21
+ afterEach(() => {
22
+ vi.clearAllMocks()
23
+ })
24
+
25
+ it('uses a relative URL and omits Host header in the browser', async () => {
26
+ await refreshTokenRequest()
27
+
28
+ expect(mockFetch).toHaveBeenCalledTimes(1)
29
+
30
+ const [url, init] = mockFetch.mock.calls[0]
31
+ expect(url).toBe('/api/vtexid/refreshtoken/webstore')
32
+ expect(init.headers).toEqual({ 'content-type': 'application/json' })
33
+ })
34
+ })