@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.
- package/dist/backend/CrudForm.js +47 -12
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/portal/PortalShell.js +1 -1
- package/dist/portal/PortalShell.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/CrudForm.tsx +70 -12
- package/src/backend/__tests__/CrudForm.fieldInjection.test.tsx +8 -1
- package/src/backend/__tests__/CrudForm.legacyInjectionBridge.test.tsx +188 -0
- package/src/portal/PortalShell.tsx +6 -1
- package/src/portal/__tests__/PortalShell.test.tsx +138 -0
|
@@ -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
|
})
|