hydrogen-sanity
Version:
Sanity.io toolkit for Hydrogen
497 lines (423 loc) • 14.7 kB
text/typescript
import {beforeEach, describe, expect, it, vi} from 'vitest'
import {createSanityContext} from '../context'
import {type AppLoadContext, PreviewSession, Session} from '../fixtures'
import {action, loader} from './route'
vi.mock('@sanity/preview-url-secret', () => ({
validatePreviewUrl: vi.fn(),
}))
vi.mock('../utils', async () => {
const actual = await vi.importActual('../utils')
return {
...actual,
sanitizePerspective: vi.fn((value) => value || 'published'),
}
})
describe('the preview route', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockValidatePreviewUrl: any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockSanitizePerspective: any
const projectId = 'my-project-id'
const dataset = 'my-dataset'
const token = 'my-token'
beforeEach(async () => {
vi.clearAllMocks()
mockValidatePreviewUrl = vi.mocked(
(await import('@sanity/preview-url-secret')).validatePreviewUrl,
)
mockSanitizePerspective = vi.mocked((await import('../utils')).sanitizePerspective)
})
describe('entering preview mode', () => {
const request = new Request('https://example.com?secret=test-secret')
it('enters preview mode successfully', async () => {
const previewSession = new PreviewSession()
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
token,
session: previewSession,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
const perspective = 'drafts'
const redirectTo = '/products'
mockValidatePreviewUrl.mockResolvedValue({
isValid: true,
redirectTo,
studioPreviewPerspective: perspective,
})
const response = await loader({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(302)
expect((response as Response).headers.get('Location')).toBe(redirectTo)
expect(previewSession.get('projectId')).toBe(projectId)
expect(previewSession.get('perspective')).toBe(perspective)
})
it('blocks access when preview mode is disabled', async () => {
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
const response = await loader({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(403)
expect(await (response as Response).text()).toMatchInlineSnapshot(
`"Preview mode is not enabled in this environment."`,
)
})
it('fails when no preview token is provided', async () => {
await expect(
createSanityContext({
request,
client: {projectId, dataset},
preview: {
token: '', // Testing with empty token to trigger the error
session: new PreviewSession(),
},
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Enabling preview mode requires a token.]`,
)
})
it('fails when project ID is missing from client config', async () => {
await expect(
createSanityContext({
request,
client: {dataset},
preview: {
token,
session: new PreviewSession(),
},
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Configuration must contain \`projectId\`]`,
)
})
it('works with Hydrogen session is provided', async () => {
const hydrogenSession = new Session()
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
session: hydrogenSession,
token,
},
})
const context: AppLoadContext = {
session: hydrogenSession,
sanity,
}
const perspective = 'drafts'
const redirectTo = '/collections'
mockValidatePreviewUrl.mockResolvedValue({
isValid: true,
redirectTo,
studioPreviewPerspective: perspective,
})
const response = await loader({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(302)
expect((response as Response).headers.get('Location')).toBe(redirectTo)
expect(hydrogenSession.get('projectId')).toBe(projectId)
expect(hydrogenSession.get('perspective')).toBe(perspective)
})
it('rejects invalid preview URLs', async () => {
const previewSession = new PreviewSession()
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
token,
session: previewSession,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
mockValidatePreviewUrl.mockResolvedValue({
isValid: false,
})
const response = await loader({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(401)
expect(await (response as Response).text()).toMatchInlineSnapshot(`"Invalid secret"`)
})
it('uses default settings when redirect URL and perspective are not specified', async () => {
const previewSession = new PreviewSession()
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
token,
session: previewSession,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
mockValidatePreviewUrl.mockResolvedValue({
isValid: true,
})
const response = await loader({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(302)
expect((response as Response).headers.get('Location')).toBe('/')
expect(previewSession.get('perspective')).toBe('drafts')
})
})
describe('exiting preview mode', () => {
it('exits preview mode with POST', async () => {
const request = new Request('https://example.com', {method: 'POST'})
const session = new Session()
session.set('projectId', projectId)
session.set('perspective', 'drafts')
const context: AppLoadContext = {
session,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanity: {} as any,
}
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(302)
expect((response as Response).headers.get('Location')).toBe('/')
expect(session.get('projectId')).toBeUndefined()
expect(session.get('perspective')).toBeUndefined()
})
it('exits preview mode with DELETE', async () => {
const request = new Request('https://example.com', {method: 'DELETE'})
const session = new Session()
session.set('projectId', projectId)
session.set('perspective', 'drafts')
const context: AppLoadContext = {
session,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanity: {} as any,
}
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(302)
expect((response as Response).headers.get('Location')).toBe('/')
expect(session.get('projectId')).toBeUndefined()
expect(session.get('perspective')).toBeUndefined()
})
it('exits preview mode when using a preview session', async () => {
const request = new Request('https://example.com', {method: 'POST'})
const previewSession = new PreviewSession()
previewSession.set('projectId', projectId)
previewSession.set('perspective', 'drafts')
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
token,
session: previewSession,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(302)
expect((response as Response).headers.get('Location')).toBe('/')
expect((response as Response).headers.get('Set-Cookie')).toBe('destroyed-cookie')
})
})
describe('changing preview perspective', () => {
it('changes the preview perspective successfully', async () => {
const formData = new FormData()
formData.set('perspective', 'published')
const request = new Request('https://example.com', {
method: 'PUT',
body: formData,
})
const previewSession = new PreviewSession()
previewSession.set('projectId', projectId)
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
token,
session: previewSession,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
mockSanitizePerspective.mockReturnValue('published')
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(200)
expect(await (response as Response).text()).toMatchInlineSnapshot(`"OK"`)
expect(previewSession.get('perspective')).toBe('published')
expect(mockSanitizePerspective).toHaveBeenCalledWith('published')
})
it('handles perspective values with multiple options', async () => {
const formData = new FormData()
formData.set('perspective', 'drafts,published')
const request = new Request('https://example.com', {
method: 'PUT',
body: formData,
})
const previewSession = new PreviewSession()
previewSession.set('projectId', projectId)
const sanity = await createSanityContext({
request: new Request('https://example.com'),
client: {projectId, dataset},
preview: {
token,
session: previewSession,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
mockSanitizePerspective.mockReturnValue(['drafts', 'published'])
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(200)
expect(previewSession.get('perspective')).toBe('drafts,published')
})
it('blocks perspective changes when preview mode is disabled', async () => {
const request = new Request('https://example.com', {method: 'PUT'})
const sanity = await createSanityContext({
request: new Request('https://example.com'),
client: {projectId, dataset},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(403)
expect(await (response as Response).text()).toMatchInlineSnapshot(
`"Preview mode is not enabled in this environment."`,
)
})
it('rejects requests when project ID in session does not match', async () => {
const request = new Request('https://example.com', {method: 'PUT'})
const previewSession = new PreviewSession()
previewSession.set('projectId', 'different-project-id')
const sanity = await createSanityContext({
request,
client: {projectId, dataset},
preview: {
session: previewSession,
token,
},
})
const context: AppLoadContext = {
session: new Session(),
sanity,
}
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(400)
expect(await (response as Response).text()).toMatchInlineSnapshot(`"Invalid projectId"`)
})
})
it('rejects unsupported HTTP methods', async () => {
const request = new Request('https://example.com', {method: 'PATCH'})
const context: AppLoadContext = {
session: new Session(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanity: {} as any,
}
const response = await action({
context,
request,
params: {},
unstable_pattern: '',
unstable_url: new URL('https://example.com'),
})
expect(response).toBeInstanceOf(Response)
expect((response as Response).status).toBe(405)
expect(await (response as Response).text()).toMatchInlineSnapshot(`"Method not allowed"`)
})
})