mirror of
https://github.com/withastro/astro.git
synced 2025-03-31 23:31:30 -05:00
fix: conflict between rewrite and actions middleware (#11052)
* fix: conflict between rewrite and actions middleware * Update middleware.ts * fix: short circuit middleware if locals already defined * chore(test): remove atkinson font refs * feat(test): progressive fallbacks * chore: remove unneeded conditional --------- Co-authored-by: bholmesdev <hey@bholmes.dev>
This commit is contained in:
parent
5a48d53385
commit
a05ca38c2c
6 changed files with 90 additions and 25 deletions
5
.changeset/khaki-papayas-knock.md
Normal file
5
.changeset/khaki-papayas-knock.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Fixes a case where rewriting would conflict with the actions internal middleware
|
|
@ -26,30 +26,68 @@ test.describe('Astro Actions - Blog', () => {
|
|||
test('Comment action - validation error', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/blog/first-post/'));
|
||||
|
||||
const authorInput = page.locator('input[name="author"]');
|
||||
const bodyInput = page.locator('textarea[name="body"]');
|
||||
const form = page.getByTestId('client');
|
||||
const authorInput = form.locator('input[name="author"]');
|
||||
const bodyInput = form.locator('textarea[name="body"]');
|
||||
|
||||
await authorInput.fill('Ben');
|
||||
await bodyInput.fill('Too short');
|
||||
|
||||
const submitButton = page.getByLabel('Post comment');
|
||||
const submitButton = form.getByRole('button');
|
||||
await submitButton.click();
|
||||
|
||||
await expect(page.locator('p[data-error="body"]')).toBeVisible();
|
||||
await expect(form.locator('p[data-error="body"]')).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
test('Comment action - progressive fallback validation error', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/blog/first-post/'));
|
||||
|
||||
const form = page.getByTestId('progressive-fallback');
|
||||
const authorInput = form.locator('input[name="author"]');
|
||||
const bodyInput = form.locator('textarea[name="body"]');
|
||||
|
||||
await authorInput.fill('Ben');
|
||||
await bodyInput.fill('Too short');
|
||||
|
||||
const submitButton = form.getByRole('button');
|
||||
await submitButton.click();
|
||||
|
||||
await expect(form.locator('p[data-error="body"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Comment action - progressive fallback success', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/blog/first-post/'));
|
||||
|
||||
const form = page.getByTestId('progressive-fallback');
|
||||
const authorInput = form.locator('input[name="author"]');
|
||||
const bodyInput = form.locator('textarea[name="body"]');
|
||||
|
||||
const body = 'Fallback - This should be long enough.';
|
||||
await authorInput.fill('Ben');
|
||||
await bodyInput.fill(body);
|
||||
|
||||
const submitButton = form.getByRole('button');
|
||||
await submitButton.click();
|
||||
|
||||
const comments = page.getByTestId('server-comments');
|
||||
await expect(comments).toBeVisible();
|
||||
await expect(comments).toContainText(body);
|
||||
});
|
||||
|
||||
test('Comment action - custom error', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/blog/first-post/?commentPostIdOverride=bogus'));
|
||||
|
||||
const authorInput = page.locator('input[name="author"]');
|
||||
const bodyInput = page.locator('textarea[name="body"]');
|
||||
const form = page.getByTestId('client');
|
||||
const authorInput = form.locator('input[name="author"]');
|
||||
const bodyInput = form.locator('textarea[name="body"]');
|
||||
await authorInput.fill('Ben');
|
||||
await bodyInput.fill('This should be long enough.');
|
||||
|
||||
const submitButton = page.getByLabel('Post comment');
|
||||
const submitButton = form.getByRole('button');
|
||||
await submitButton.click();
|
||||
|
||||
const unexpectedError = page.locator('p[data-error="unexpected"]');
|
||||
const unexpectedError = form.locator('p[data-error="unexpected"]');
|
||||
await expect(unexpectedError).toBeVisible();
|
||||
await expect(unexpectedError).toContainText('NOT_FOUND: Post not found');
|
||||
});
|
||||
|
@ -57,18 +95,19 @@ test.describe('Astro Actions - Blog', () => {
|
|||
test('Comment action - success', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/blog/first-post/'));
|
||||
|
||||
const authorInput = page.locator('input[name="author"]');
|
||||
const bodyInput = page.locator('textarea[name="body"]');
|
||||
const form = page.getByTestId('client');
|
||||
const authorInput = form.locator('input[name="author"]');
|
||||
const bodyInput = form.locator('textarea[name="body"]');
|
||||
|
||||
const body = 'This should be long enough.';
|
||||
const body = 'Client: This should be long enough.';
|
||||
await authorInput.fill('Ben');
|
||||
await bodyInput.fill(body);
|
||||
|
||||
const submitButton = page.getByLabel('Post comment');
|
||||
const submitButton = form.getByRole('button');
|
||||
await submitButton.click();
|
||||
|
||||
const comment = await page.getByTestId('comment');
|
||||
await expect(comment).toBeVisible();
|
||||
await expect(comment).toContainText(body);
|
||||
const comments = page.getByTestId('client-comments');
|
||||
await expect(comments).toBeVisible();
|
||||
await expect(comments).toContainText(body);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,10 +20,6 @@ const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props;
|
|||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- Font preloads -->
|
||||
<link rel="preload" href="/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ export function PostComment({
|
|||
<>
|
||||
<form
|
||||
method="POST"
|
||||
data-testid="client"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target as HTMLFormElement;
|
||||
|
@ -34,7 +35,7 @@ export function PostComment({
|
|||
{unexpectedError && <p data-error="unexpected" style={{ color: 'red' }}>{unexpectedError}</p>}
|
||||
<input {...getActionProps(actions.blog.comment)} />
|
||||
<input type="hidden" name="postId" value={postId} />
|
||||
<label className="sr-only" htmlFor="author">
|
||||
<label htmlFor="author">
|
||||
Author
|
||||
</label>
|
||||
<input id="author" type="text" name="author" placeholder="Your name" />
|
||||
|
@ -44,13 +45,13 @@ export function PostComment({
|
|||
{bodyError}
|
||||
</p>
|
||||
)}
|
||||
<button aria-label="Post comment" type="submit">
|
||||
<button type="submit">
|
||||
Post
|
||||
</button>
|
||||
</form>
|
||||
<div data-testid="client-comments">
|
||||
{comments.map((c) => (
|
||||
<article
|
||||
data-testid="comment"
|
||||
key={c.body}
|
||||
style={{
|
||||
border: '2px solid color-mix(in srgb, var(--accent), transparent 80%)',
|
||||
|
@ -63,6 +64,7 @@ export function PostComment({
|
|||
<p>{c.author}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import BlogPost from '../../layouts/BlogPost.astro';
|
|||
import { db, eq, Comment, Likes } from 'astro:db';
|
||||
import { Like } from '../../components/Like';
|
||||
import { PostComment } from '../../components/PostComment';
|
||||
import { actions } from 'astro:actions';
|
||||
import { actions, getActionProps } from 'astro:actions';
|
||||
import { isInputError } from 'astro:actions';
|
||||
|
||||
export const prerender = false;
|
||||
|
@ -45,7 +45,23 @@ const commentPostIdOverride = Astro.url.searchParams.get('commentPostIdOverride'
|
|||
: undefined}
|
||||
client:load
|
||||
/>
|
||||
<div>
|
||||
<form method="POST" data-testid="progressive-fallback">
|
||||
<input {...getActionProps(actions.blog.comment)} />
|
||||
<input type="hidden" name="postId" value={post.id} />
|
||||
<label for="fallback-author">
|
||||
Author
|
||||
</label>
|
||||
<input id="fallback-author" type="text" name="author" required />
|
||||
<label for="fallback-body" class="sr-only">
|
||||
Comment
|
||||
</label>
|
||||
<textarea id="fallback-body" rows={10} name="body" required></textarea>
|
||||
{isInputError(comment?.error) && comment.error.fields.body && (
|
||||
<p class="error" data-error="body">{comment.error.fields.body.toString()}</p>
|
||||
)}
|
||||
<button type="submit">Post Comment</button>
|
||||
</form>
|
||||
<div data-testid="server-comments">
|
||||
{
|
||||
comments.map((c) => (
|
||||
<article>
|
||||
|
|
|
@ -12,14 +12,21 @@ export type Locals = {
|
|||
|
||||
export const onRequest = defineMiddleware(async (context, next) => {
|
||||
const locals = context.locals as Locals;
|
||||
// Actions middleware may have run already after a path rewrite.
|
||||
// See https://github.com/withastro/roadmap/blob/feat/reroute/proposals/0047-rerouting.md#ctxrewrite
|
||||
// `_actionsInternal` is the same for every page,
|
||||
// so short circuit if already defined.
|
||||
if (locals._actionsInternal) return next();
|
||||
|
||||
const { request, url } = context;
|
||||
const contentType = request.headers.get('Content-Type');
|
||||
|
||||
// Avoid double-handling with middleware when calling actions directly.
|
||||
if (url.pathname.startsWith('/_actions')) return nextWithLocalsStub(next, locals);
|
||||
|
||||
if (!contentType || !hasContentType(contentType, formContentTypes))
|
||||
if (!contentType || !hasContentType(contentType, formContentTypes)) {
|
||||
return nextWithLocalsStub(next, locals);
|
||||
}
|
||||
|
||||
const formData = await request.clone().formData();
|
||||
const actionPath = formData.get('_astroAction');
|
||||
|
|
Loading…
Add table
Reference in a new issue