@open-mercato/ui 0.8.1-develop.7237.1.635faf2c7a → 0.8.1-develop.7239.1.d90c352076

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.
@@ -4,7 +4,23 @@
4
4
 
5
5
  import * as React from 'react'
6
6
  import { act, render, screen, waitFor } from '@testing-library/react'
7
+ import type { CustomerAuthContext } from '@open-mercato/shared/modules/customer-auth'
7
8
  import { PortalShell } from '../PortalShell'
9
+ import { PortalProvider } from '../PortalContext'
10
+ import { PortalLayoutShell } from '../PortalLayoutShell'
11
+
12
+ const signedInAuth: CustomerAuthContext = {
13
+ sub: 'customer-1',
14
+ sid: 'session-1',
15
+ type: 'customer',
16
+ tenantId: 'tenant-1',
17
+ orgId: 'org-1',
18
+ email: 'ada@example.com',
19
+ displayName: 'Ada Lovelace',
20
+ customerEntityId: null,
21
+ personEntityId: null,
22
+ resolvedFeatures: [],
23
+ }
8
24
 
9
25
  const apiCallMock = jest.fn()
10
26
 
@@ -151,4 +167,126 @@ describe('PortalShell', () => {
151
167
 
152
168
  expect(screen.queryByRole('navigation', { name: 'Portal navigation' })).not.toBeInTheDocument()
153
169
  })
170
+
171
+ // The layout that supplies `authenticated` sits above the [...slug] segment, so a
172
+ // client-side navigation leaves a stale `false` behind. Trusting it painted the
173
+ // logged-out header over authenticated content (#5678).
174
+ it('renders the authenticated chrome when the context holds a user and the prop says otherwise', async () => {
175
+ apiCallMock.mockImplementation(async (url: string) => {
176
+ if (url === '/api/customer_accounts/portal/profile') {
177
+ return {
178
+ ok: true,
179
+ status: 200,
180
+ result: {
181
+ ok: true,
182
+ user: {
183
+ id: 'customer-1',
184
+ email: 'ada@example.com',
185
+ displayName: 'Ada Lovelace',
186
+ emailVerified: true,
187
+ customerEntityId: null,
188
+ personEntityId: null,
189
+ isActive: true,
190
+ lastLoginAt: null,
191
+ createdAt: '',
192
+ },
193
+ roles: [],
194
+ resolvedFeatures: [],
195
+ isPortalAdmin: false,
196
+ },
197
+ }
198
+ }
199
+ return { ok: true, status: 200, result: { ok: true, groups: [] } }
200
+ })
201
+
202
+ render(
203
+ <PortalProvider
204
+ orgSlug="acme"
205
+ initialAuth={signedInAuth}
206
+ initialTenant={{ tenantId: 'tenant-1', organizationId: 'org-1', organizationName: 'Acme' }}
207
+ >
208
+ <PortalShell authenticated={false} orgSlug="acme" organizationName="Acme">
209
+ <div>Portal content</div>
210
+ </PortalShell>
211
+ </PortalProvider>,
212
+ )
213
+
214
+ await waitFor(() => {
215
+ expect(screen.getByTestId('portal-nav-ready')).toBeInTheDocument()
216
+ })
217
+
218
+ expect(screen.getByRole('button', { name: 'Log Out' })).toBeInTheDocument()
219
+ expect(screen.queryByRole('link', { name: 'Log In' })).not.toBeInTheDocument()
220
+ expect(screen.queryByRole('link', { name: 'Sign Up' })).not.toBeInTheDocument()
221
+ })
222
+
223
+ it('keeps the public chrome when no context user backs the prop', async () => {
224
+ render(
225
+ <PortalShell authenticated={false} orgSlug="acme" organizationName="Acme">
226
+ <div>Portal content</div>
227
+ </PortalShell>,
228
+ )
229
+
230
+ expect(screen.getByRole('link', { name: 'Log In' })).toHaveAttribute('href', '/acme/portal/login')
231
+ expect(screen.queryByTestId('portal-nav-ready')).not.toBeInTheDocument()
232
+ expect(apiCallMock).not.toHaveBeenCalled()
233
+ })
234
+
235
+ // The composed path — the real props the (frontend) layout passes for an auth
236
+ // route — is the seam where the two halves of the #5678 fix meet. Withholding
237
+ // the session there is what keeps the context from upgrading the chrome.
238
+ it('renders the public chrome for the props the layout passes on an auth route', async () => {
239
+ render(
240
+ <PortalLayoutShell
241
+ orgSlug="acme"
242
+ organizationName="Acme"
243
+ tenantId="tenant-1"
244
+ organizationId="org-1"
245
+ authenticated={false}
246
+ userName={null}
247
+ userEmail={null}
248
+ customerAuth={null}
249
+ >
250
+ <div>Login form</div>
251
+ </PortalLayoutShell>,
252
+ )
253
+
254
+ expect(screen.getByRole('link', { name: 'Log In' })).toHaveAttribute('href', '/acme/portal/login')
255
+ expect(screen.getByRole('link', { name: 'Sign Up' })).toHaveAttribute('href', '/acme/portal/signup')
256
+ expect(screen.queryByTestId('portal-nav-ready')).not.toBeInTheDocument()
257
+ expect(screen.queryByRole('button', { name: 'Log Out' })).not.toBeInTheDocument()
258
+
259
+ // Neither the portal nav nor the profile endpoint may be reached from a
260
+ // public page: PortalProvider treats a null `customerAuth` as "server
261
+ // confirmed: no session" and skips its enrichment fetch entirely.
262
+ await waitFor(() => {
263
+ expect(apiCallMock).not.toHaveBeenCalled()
264
+ })
265
+ })
266
+
267
+ it('renders the authenticated chrome for the props the layout passes on a signed-in route', async () => {
268
+ apiCallMock.mockResolvedValue({ ok: true, status: 200, result: { ok: true, groups: [] } })
269
+
270
+ render(
271
+ <PortalLayoutShell
272
+ orgSlug="acme"
273
+ organizationName="Acme"
274
+ tenantId="tenant-1"
275
+ organizationId="org-1"
276
+ authenticated
277
+ userName="Ada Lovelace"
278
+ userEmail="ada@example.com"
279
+ customerAuth={signedInAuth}
280
+ >
281
+ <div>Dashboard</div>
282
+ </PortalLayoutShell>,
283
+ )
284
+
285
+ await waitFor(() => {
286
+ expect(screen.getByTestId('portal-nav-ready')).toBeInTheDocument()
287
+ })
288
+
289
+ expect(screen.getByRole('button', { name: 'Log Out' })).toBeInTheDocument()
290
+ expect(screen.queryByRole('link', { name: 'Log In' })).not.toBeInTheDocument()
291
+ })
154
292
  })