Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ SMOKE_BASE_URL=http://localhost:3000
SMOKE_USER_EMAIL=
SMOKE_USER_PASSWORD=

# Content the specs navigate to. Ids only - /grant/<id> and /proposal/<id>
SMOKE_GRANT_ID=33
SMOKE_PROPOSAL_ID=5
# Content the specs navigate to. Post ids only - /grant/<id> and /proposal/<id>.
# Both must be owned by the smoke account: the title specs edit them, and
# editing is gated on authorship.
SMOKE_GRANT_POST_ID=662
SMOKE_PROPOSAL_POST_ID=663
3 changes: 3 additions & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
SMOKE_BASE_URL: ${{ inputs.base_url || vars.SMOKE_BASE_URL }}
SMOKE_USER_EMAIL: ${{ secrets.SMOKE_USER_EMAIL }}
SMOKE_USER_PASSWORD: ${{ secrets.SMOKE_USER_PASSWORD }}
# Ids of the fixture content, which is public.
SMOKE_GRANT_POST_ID: ${{ vars.SMOKE_GRANT_POST_ID }}
SMOKE_PROPOSAL_POST_ID: ${{ vars.SMOKE_PROPOSAL_POST_ID }}
run: npm run test:smoke

- name: Upload report
Expand Down
1 change: 1 addition & 0 deletions components/Comment/CollapsedCommentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const CollapsedCommentEditor = ({ commentType, onExpand }: CollapsedComme
type="button"
onClick={onExpand}
className="group w-full flex items-center gap-3 rounded-lg border border-blue-200 bg-blue-50/40 px-4 py-3 text-left transition-colors hover:border-blue-400 hover:bg-blue-50"
data-testid="comment-editor-collapsed"
>
<Avatar
src={authorProfile?.profileImage}
Expand Down
5 changes: 4 additions & 1 deletion components/Comment/CommentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,10 @@ export const CommentEditor = ({
}

const renderEditor = (canReview: boolean, banner: React.ReactNode = null) => (
<div className="relative border border-gray-200 rounded-lg overflow-hidden bg-white focus-within:ring-blue-500 focus-within:border-blue-500 transition-all duration-200">
<div
className="relative border border-gray-200 rounded-lg overflow-hidden bg-white focus-within:ring-blue-500 focus-within:border-blue-500 transition-all duration-200"
data-testid="comment-editor"
>
{/* User info header */}
{showHeader && (
<EditorHeader
Expand Down
5 changes: 4 additions & 1 deletion components/Comment/CommentEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ export const CommentEmptyState = ({
);

return (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div
className="flex flex-col items-center justify-center py-12 text-center"
data-testid="comment-empty-state"
>
<div className="mb-4 rounded-full bg-gray-100 p-3">{icon}</div>
<h3 className="mb-2 text-lg font-medium text-gray-900">{message}</h3>
<p className="text-sm text-gray-500">{description}</p>
Expand Down
4 changes: 2 additions & 2 deletions components/Comment/CommentItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export const CommentItem = ({

{/* If we're replying, show the reply editor */}
{isReplying && !readOnly && (
<div className="mt-4 border-t pt-4 px-4 pb-4">
<div className="mt-4 border-t pt-4 px-4 pb-4" data-testid="comment-reply-editor">
<h4 className="text-sm font-medium mb-2">Your reply:</h4>
<CommentEditor
onSubmit={handleReply}
Expand All @@ -328,7 +328,7 @@ export const CommentItem = ({
};

return (
<div className="mt-4" id={`comment-${comment.id}`}>
<div className="mt-4" id={`comment-${comment.id}`} data-testid="comment-item">
{/* Debug information for comment ID */}
{showDebugInfo && (
<div className="bg-gray-100 p-2 mb-2 rounded-md border border-gray-300">
Expand Down
2 changes: 1 addition & 1 deletion components/Comment/CommentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const CommentList: React.FC<CommentListProps> = ({
readOnly = false,
}) => {
return (
<div className="space-y-8">
<div className="space-y-8" data-testid={isRootList ? 'comment-list' : undefined}>
{comments.map((comment) => (
<CommentItem
commentType={commentType}
Expand Down
8 changes: 7 additions & 1 deletion components/Comment/CommentSortAndFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ export const CommentSortAndFilters: FC<CommentSortAndFiltersProps> = ({
<BaseMenu
align="start"
trigger={
<Button variant="outlined" size="sm" className="flex items-center gap-1">
<Button
variant="outlined"
size="sm"
className="flex items-center gap-1"
data-testid="comment-sort-trigger"
>
{(() => {
const currentOption = sortOptions.find((option) => option.value === sortBy);
const Icon = currentOption?.icon || Star;
Expand All @@ -62,6 +67,7 @@ export const CommentSortAndFilters: FC<CommentSortAndFiltersProps> = ({
key={option.value}
onClick={() => handleSortChange(option.value)}
className={sortBy === option.value ? 'bg-gray-100' : ''}
data-testid={`comment-sort-option-${option.value}`}
>
<div className="flex items-center gap-2">
<Icon className="h-4 w-4" />
Expand Down
7 changes: 5 additions & 2 deletions components/Comment/components/EditorFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ export const EditorFooter = ({
<div className="flex flex-col-reverse mobile:!flex-row justify-between items-start mobile:!items-center px-4 py-2 border-t border-gray-200 gap-2 mobile:!gap-0">
{/* Left section: Draft status and word count */}
<div className="flex items-center gap-3 text-xs text-gray-500">
{saveStatus === 'saved' && lastSaved && <span>Draft saved {formatLastSaved()}</span>}
{saveStatus === 'saving' && <span>Saving draft...</span>}
{saveStatus === 'saved' && lastSaved && (
<span data-testid="comment-draft-status">Draft saved {formatLastSaved()}</span>
)}
{saveStatus === 'saving' && <span data-testid="comment-draft-status">Saving draft...</span>}
{showWordCount && (
<span className={isOverLimit ? 'text-red-600 font-medium' : ''}>
{wordCount.toLocaleString()} / {wordLimit.toLocaleString()} words
Expand Down Expand Up @@ -78,6 +80,7 @@ export const EditorFooter = ({
size="sm"
onClick={onSubmit}
disabled={isSubmitting || !canSubmit}
data-testid="comment-editor-submit"
>
<span className="flex items-center justify-between w-full gap-3">
<span>{isSubmitting ? 'Submitting...' : 'Submit'}</span>
Expand Down
10 changes: 10 additions & 0 deletions components/Feed/FeedItemActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ interface ActionButtonProps {
hideIcon?: boolean;
/** Bare icon and count instead of the pill chrome. */
flat?: boolean;
testId?: string;
}

// Export ActionButton so it can be used in other components
Expand All @@ -116,8 +117,10 @@ export const ActionButton: FC<ActionButtonProps> = ({
showTooltip = true,
hideIcon = false,
flat = false,
testId,
}) => (
<Button
data-testid={testId}
variant="ghost"
size="sm"
className={cn(
Expand Down Expand Up @@ -503,9 +506,13 @@ export const FeedItemActions: FC<FeedItemActionsProps> = ({
)
) : null;

// Note the test id rather than the label: FeedItemComment renders this whole
// bar inside an aria-hidden wrapper, which takes every control in it out of
// the accessibility tree and so out of reach of a role-based locator.
const moreMenuTrigger = isFlat ? (
<button
type="button"
data-testid="feed-item-more-options"
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
className="flex h-6 w-6 items-center justify-center text-gray-500 transition-colors hover:text-gray-800"
Expand All @@ -515,6 +522,7 @@ export const FeedItemActions: FC<FeedItemActionsProps> = ({
</button>
) : (
<Button
data-testid="feed-item-more-options"
onMouseDown={(e) => {
e.stopPropagation();
}}
Expand All @@ -524,6 +532,7 @@ export const FeedItemActions: FC<FeedItemActionsProps> = ({
variant="ghost"
size="sm"
className="flex h-8 w-8 !p-0 items-center justify-center rounded-full text-gray-700 transition-all hover:bg-white hover:text-gray-900 hover:shadow-sm"
aria-label="More options"
>
<MoreHorizontal className="h-[18px] w-[18px]" />
</Button>
Expand Down Expand Up @@ -664,6 +673,7 @@ export const FeedItemActions: FC<FeedItemActionsProps> = ({
showLabel={Boolean(actionLabels?.comment)}
showTooltip={showTooltips}
flat={isFlat}
testId="feed-item-comment-action"
/>
)}
{(onTip || totalAwarded > 0) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function GrantDescriptionSection() {
<SectionHeader icon={FileText}>Short Description</SectionHeader>
<div className="mt-2">
<Textarea
data-testid="grant-description-input"
{...register('shortDescription')}
placeholder="Describe what this RFP is for and what you're looking to fund"
error={errors.shortDescription?.message?.toString()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function GrantFundingAmountSection() {
<SectionHeader icon={DollarSign}>Funding Amount</SectionHeader>
<div className="mt-2">
<Input
data-testid="grant-amount-input"
{...register('budget')}
placeholder="10,000"
type="text"
Expand Down
18 changes: 14 additions & 4 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,27 @@ if (!baseURL) {
);
}

const isLocalServer = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/i.test(baseURL);

export default defineConfig({
testDir: './smoke',
// Deployed environments are slower than local, and these tests wait on a
// round trip through Next.js, the Django API and Postgres.
timeout: 60_000,
expect: { timeout: 15_000 },
retries: process.env.CI ? 1 : 0,
// Playwright parallelises by file, and concurrent browsers overwhelm a local
// dev server: three at once pushed every test near the 60s timeout. The suite
// is small enough that running it serially costs little and stays reliable.
workers: 1,
// Playwright parallelises by file, which takes the wall clock from the sum of
// the files to the longest one — worth a lot here, where the notebook specs
// run minutes and the rest run seconds.
//
// Only off against a local dev server, which compiles routes on demand and
// buckles under concurrent browsers: three at once pushed every test near the
// 60s timeout. A deployed environment serves those routes prebuilt.
//
// Files that must not interleave say so themselves, with
// `test.describe.configure({ mode: 'serial' })`; nothing here depends on the
// worker count for ordering.
workers: isLocalServer ? 1 : 3,
reporter: [['list'], ['html', { open: 'never' }]],
use: {
baseURL,
Expand Down
Loading
Loading