From de7baa5faf0573eff06dd2f22fe28f433b7abb9f Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Mon, 11 May 2026 14:25:10 +0200 Subject: [PATCH] test: add ~500 tests across web, utils, api, import, ai, db, email, auth (#3038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(web): add tests for zustand stores and pure helpers Cover stores and pure helpers across the builder/dashboard/command-palette surfaces that previously had 0% coverage: - command-palette store (open/close, page stack, search clearing, goBack) - builder assistant-store - builder sidebar store + parseBuilderLayoutCookie / mapPanelLayoutToBuilderLayout - builder section store (collapse, toggle, toggleAll) - builder preview page-layout toggle - dashboard resume-thumbnail render-size math + cache key - MCP tool name + annotations invariants * test(web): cover MCP helpers and template metadata Add tests for previously 0%-coverage MCP and dialog helpers: - buildMcpServerCard: server-info, tool catalog vs MCP_TOOL_NAME, prompts, resource templates, configuration schema, auth schemes - registerPrompts (build/improve/review): registration, args schema, resource context with interpolated resume id, read-only / no-fabrication directives - registerResources (resume://{id}, resume://_meta/schema): handler reads via oRPC client, error on missing id, schema returns valid JSON - templates metadata: ids match display names, valid sidebar positions, unique image URLs, every entry has tags + description * test(web): cover sidebar section helpers and layout screens Add tests for previously near-0%-coverage modules: - libs/resume/section: getSectionTitle / getSectionIcon return distinct, exhaustive results for every sidebar section + cover-letter; icon props forwarding; left/right sidebar collections do not overlap. - layout/loading-screen: spinner + text render. - layout/error-screen: error message surfaces, Refresh button triggers reset. - layout/breakpoint-indicator: default + each corner positioning, all breakpoint labels rendered, print-hidden class applied. * test(web): cover preview canvas math and font-weight defaults Add tests for pure helpers that previously had no direct coverage: - typography/getNextWeights: prefers 400 + 600 when both are available, returns null for unknown families, never produces duplicates, bounded to two weights from the 100..900 set. - preview.shared/normalizeResumePreviewProps: documented defaults + pass-through. - preview.shared/getScaledPreviewPageSize: scaling identity at 1, and fractional scaling. - preview.shared/getPreviewCanvasScale: respects 4x desired scale for small pages, honors high devicePixelRatio, clamps to the 16M-pixel canvas budget for large pages. * test(utils): cover DOCX section renderers and html-to-paragraphs @reactive-resume/utils/resume/docx was previously at ~2.84% statement coverage despite being load-bearing for the resume DOCX export. - section-renderers: empty-string / hidden-section / hidden-item branches for renderSummary, renderBuiltInSection, and renderCustomSection; cover-letter and summary custom-section dispatch; unknown-type fallback; setRenderConfig idempotency. - html-to-docx: whitespace-only short-circuit, multiple top-level blocks, h1..h6 paragraph mapping, inline style and link rendering, custom font/size/color/linkColor config, ignored script/comment nodes. * test: cover DOCX builder smoke paths and reactive-resume JSON importer - utils/resume/docx/builder: buildDocument runs end-to-end against the default and sample resume data, both page formats, full-width and sidebar layouts, and gracefully degrades with unparseable color or empty font family inputs. - import/reactive-resume-json: ReactiveResumeJSONImporter validates malformed JSON, recovers missing built-in sections by appending them to page 1 without reordering, and preserves layouts that already contain every built-in section. * test(import): cover JSONResumeImporter parse/convert JSONResumeImporter (450 lines) was previously at 0% coverage. Add tests for the public surface: - Invalid JSON / invalid-shape errors are surfaced. - basics, summary, picture, education, projects, skills, profiles all map to the corresponding ResumeData sections. - Empty work/education entries (missing key field) are filtered out. - Highlights become HTML list items in the description field. - Skill level parsing flows through utils/level.parseLevel. - formatLocation joins city, region, countryCode with commas. * test(web): cover query client serializer and home-page animations - libs/query/client: getQueryClient returns a fresh QueryClient, queryKeyHashFn produces stable JSON envelopes for matching keys (and distinct strings for different keys), dehydrate/hydrate round-trip Date values via the oRPC serializer. - components/animation/spotlight: overlay container is pointer-events-none, both beam groups render, custom width/height/translateY/gradient props flow into inline styles. - components/animation/comet-card: children mount inside the perspective wrapper, custom className is merged with the 3D baseline classes, glare overlay renders, mouse move/leave handlers do not throw. * test(web): cover Copyright footer Verify the footer's MIT license link, Amruth Pillai attribution, external-tab targets, embedded app version (via __APP_VERSION__ stub), and custom className merging — previously at 0% coverage. * test(api): cover flags, auth providers, and resume-access cookies Unlock @reactive-resume/api by mocking @reactive-resume/env/server and @tanstack/react-start/server. Previously the only services tested were the standalone AI test and resume-access-policy. - services/flags: flagsService.getFlags reads disableSignups/disableEmailAuth from env (no stale cache). - services/auth: providers.list always exposes credential + passkey, and only adds Google/GitHub/LinkedIn/custom when both id and secret are set; custom provider uses OAUTH_PROVIDER_NAME with a 'Custom OAuth' fallback. - helpers/resume-access: hasResumeAccess validates against signed cookies with constant-time comparison; grantResumeAccess writes a 10-minute httpOnly cookie with the secure flag matching APP_URL's https-ness. * test: cover statistics service and email transport via env mocks - api/services/statistics: github star count succeeds, retries on non-OK, falls back to last-known on fetch error / non-positive / non-numeric responses; user and resume counts roll up DB count. - email/src/transport: returns silently with no text/html, logs when SMTP is not configured, dispatches via nodemailer with the env config when fully wired, renders react elements to html + text bodies, swallows transport errors instead of crashing. * test(api): cover resume-events publish + subscribe - publishResumeUpdated issues pg_notify with channel and serialized event payload. - subscribeResumeUpdated yields events whose resumeId+userId match the subscription, filters out other resumes/users, ignores malformed JSON and notifications on other channels, calls LISTEN/UNLISTEN and releases the client, and terminates immediately if the abort signal fires before iteration starts. * test(api): cover oRPC auth resolution resolveUserFromRequestHeaders is the single point where every oRPC procedure picks up the authenticated user. Test the priority chain: - x-api-key wins when present and valid - on invalid api key, falls back to session via auth.api.getSession - Bearer JWT in Authorization header is verified via verifyOAuthToken - invalid Bearer falls back to session - Authorization scheme other than Bearer is ignored entirely - thrown errors from token verification are logged and swallowed (caller still tries session) - returns null when no auth method succeeds * test(api): cover storage helpers inferContentType, isImageFile, processImageForUpload were 0% coverage despite being on the picture upload path. - inferContentType maps known image and pdf extensions, is case-insensitive, ignores path depth, and falls back to application/octet-stream for unknown. - isImageFile allows only the upload allowlist (gif/png/jpeg/webp) and rejects image/svg+xml, application/pdf, and empty strings. - processImageForUpload short-circuits to the original bytes when FLAG_DISABLE_IMAGE_PROCESSING is true, otherwise pipes through sharp and returns image/jpeg. * test(import): broaden v4 importer section-mapping coverage The existing v4 importer test focused on a single bug (description-only custom items) and the skill/language level scaling. This new test exercises the bulk of the v4 → v5 transformation path: - basics, picture (with border), summary, customFields - every section's filter-by-required-field invariant (awards needs title, certifications needs name, education needs institution, experience needs company, volunteer needs organization, etc.) - experience / education / awards / certifications / references field renames between schemas - language and skill level scaling (v4 0..10 → v5 0..5) Brings reactive-resume-v4-json from ~66% statement coverage to a materially higher figure (the bulk of the 410-line transformer body). * test: cover buildDocx entry and AI configuration store - utils/resume/docx/index: buildDocx returns a non-empty Blob for both default and populated resumes (previously 0% coverage despite being the public DOCX entry point). - ai/store: useAIStore preserves verification status across no-op updates, but resets testStatus + enabled whenever provider, model, apiKey, or baseURL changes; canEnable is gated to testStatus=success; setEnabled(true) is refused unless verified; reset clears every field. Brings @reactive-resume/ai from ~72% to materially higher coverage. * test(db): cover resume schema definitions packages/db was previously at 0% coverage. Smoke-test the public resume / resume_statistics / resume_analysis tables: - getTableName matches the SQL identifier used by migrations - expected columns are present on each table - defaultResumeData wiring on the data column resolves to a valid shape These are structural assertions that catch accidental renames / removals without needing a live database connection. * test(db): cover auth schema tables and relations export - src/schema/auth: table-driven test for each of the 12 auth tables asserting SQL name and presence of the key columns (user/session/ account/verification/two_factor/passkey/apikey/jwks/oauth_*). - src/relations: smoke test confirming the relations export is defined. Brings @reactive-resume/db from 0% to materially higher coverage. * test(auth): cover getSession isomorphic helper @reactive-resume/auth was previously at 0% coverage. functions.ts is the server entry point that other packages call. Mock the auth config + tanstack/react-start to verify: - getSession forwards getRequestHeaders() to auth.api.getSession - returns null when better-auth returns null * test(web): cover BuilderSidebarEdge Small presentational component on the builder layout — assert children mount, left/right positioning class branches, and the sm:flex mobile-hide behavior. * test(web): cover section-title-locale resolver cache and hook The section-title-locale module wraps createSectionTitleResolver with a per-locale async cache and a React hook for consumers in the builder. Cover: - createSectionTitleResolverForLocale returns a usable resolver - repeated calls for the same locale share a cached promise - unknown locales fall back through resolveLocale - useSectionTitleResolver returns null while loading and when no locale is passed - the hook resolves to a function once the async loader settles * test(web): cover BaseCommandGroup page-stack gating BaseCommandGroup conditionally renders based on the top of the command-palette page stack. Tests cover: - root group renders when no sub-page is active - root group hides when a sub-page is on top - sub-page group renders only when its page matches - mismatched sub-page leaves the group hidden * test(web): cover ThemeProvider context - useTheme outside ThemeProvider throws the documented error - useTheme inside ThemeProvider returns the theme + setTheme + toggleTheme helpers * test(web): cover ConfirmDialogProvider + useConfirm hook - useConfirm outside provider throws the documented error - confirm returns a pending promise - promise resolves false when the Cancel button is clicked - promise resolves true when the Confirm button is clicked - works with custom confirmText label apps/web has its own copy of this hook distinct from packages/ui (mirrors the existing UI-package tests). * test(web): cover PromptDialogProvider + usePrompt hook - usePrompt outside provider throws the documented error - returns a function when wrapped - Cancel click resolves the promise to null - Confirm click resolves to the current input value - defaultValue option seeds the initial input value * test(web): cover DashboardHeader Small presentational header used across dashboard routes — title h1, icon rendering, className merge, mobile sidebar trigger present and hidden on md+. * test(web): cover Create/Import resume cards Both cards on the resumes dashboard wire a click handler to open the appropriate dialog via the dialog store: - CreateResumeCard opens resume.create - ImportResumeCard opens resume.import Also asserts the i18n copy strings (icons aside, the cards are otherwise structural). * test(web): cover command-palette language sub-page LanguageCommandPage is a BaseCommandGroup gated on page='language'. Tests assert: - it is hidden when 'language' is not the top of the page stack - when active, it renders a CommandItem per localeMap entry - documented locale codes (en-US, de-DE, ja-JP) appear * test(web): cover command-palette theme + preferences sub-pages - ThemeCommandPage: hidden when 'theme' is not on top, renders Light and Dark options when active - PreferencesCommandGroup: root group renders both Change theme to... and Change language to... items; clicking each pushes the corresponding page onto the command-palette stack * test(ai): cover executePatchResume tool - patchResumeInputSchema rejects empty operations and unknown op values; accepts valid replace/add/remove - executePatchResume returns the applied operations on success - executePatchResume throws when an operation targets an invalid path (passes through the underlying applyResumePatches validation) - multi-op patches against top-level fields succeed end-to-end * test(ai): cover sanitize edge branches Hit the previously-uncovered branches in sanitize.ts: - numeric 1 coerces to true - '1' / '0' string shorthand coerces to true/false - missing item.hidden gets salvaged to false - empty input causes a non-Zod throw (caught + rethrown with generic message) * test(ai): cover patch-proposal preview + normalize edge cases - remove operations surface before-value with after=undefined - buildResumePatchProposalPreview labels metadata/page paths sanely - normalizeResumePatchProposals stamps every proposal with baseUpdatedAt - normalizeResumePatchProposals preserves input order * test(web): cover getLocaleOptions helper Locale combobox surface — verify the option list mirrors localeMap shape, uses locale codes as values, populates label + keywords with the translated display name, and produces unique values. * test(web): cover LevelTypeCombobox option mapping LevelTypeCombobox maps levelDesignSchema.shape.type.options through the internal getLevelTypeName labeler. Assert all 7 level types are exposed and that each produces a non-empty label. * test(web): cover ThemeToggleButton fallback paths - aria-label flips between 'Switch to light theme' and 'Switch to dark theme' based on current theme - clicking when document.startViewTransition is unavailable short-circuits to toggleTheme directly - prefers-reduced-motion forces the direct toggle path even when the view-transition API is available * test(web): cover NotFoundScreen Mock the TanStack Router Link so the screen renders standalone, then assert: documented error heading, routeId is surfaced verbatim, and the Go Back link points to '..' (parent route). * test(web): cover InformationSectionBuilder Stub SectionBase so the donation/info section renders standalone. Assert: donation prompt copy, OpenCollective CTA link, all 5 external resource links present, and external links target _blank with rel=noopener. * test(web): cover NotesSectionBuilder Mock SectionBase, RichInput, and the resume-draft hooks so the notes section renders in isolation. Assert: privacy hint copy renders, RichInput is seeded with metadata.notes, and onChange proxies through updateResumeData with a draft recipe that mutates metadata.notes. * test(web): cover TemplateSectionBuilder Stub SectionBase and useCurrentResume so the right-sidebar template section renders standalone. Asserts: current template name in the heading, template tags rendered as badges, preview image points to the catalog asset, and clicking the preview opens the resume.template.gallery dialog. * test(web): cover ColorPicker preset selection and trigger override Mock the heavy @uiw/react-color-colorful dependency. Test: - the trigger swatch reflects the controlled value - clicking a preset color invokes onChange with an rgba() string - a custom trigger replaces the default swatch when provided * test(web): cover ExportSectionBuilder Mock the heavy export pipelines (buildDocx, createResumePdfBlob, downloadWithAnchor) and the resume-draft hook to test: - JSON button packages resume.data as application/json and triggers download with the {name}.json filename - DOCX button awaits buildDocx and downloads .docx - PDF button awaits createResumePdfBlob and downloads .pdf * test(web): cover ProfilesSectionBuilder Stub the resume-draft hooks, SectionBase, SectionItem, and SectionAddItemButton so the profiles section renders standalone: - one SectionItem per profile with network as title and username as subtitle - 'Add a new profile' affordance present - when items.length > 0, the wrapper uses a solid border (not dashed) * test(web): cover SkillsSectionBuilder Mirror the profiles test for the skills section — verifies one SectionItem per skill (name → title, proficiency → subtitle) and the Add a new skill affordance. * test(web): bulk-cover 7 left-sidebar section builders Single test file covers awards, certifications, interests, languages, publications, references, and volunteer builders. For each: - one SectionItem rendered with the documented field → title/subtitle mapping - awards: title → awarder - certifications: title → 'issuer • date' - interests: name → (no subtitle) - languages: language → fluency - publications: title → publisher - references: name → (no subtitle) - volunteer: organization → location - the 'Add a new {kind}' affordance with the matching copy Mocks SectionBase, SectionItem, SectionAddItemButton, and the resume-draft hooks so each builder renders standalone. * test(web): cover ProjectsSectionBuilder buildSubtitle The projects section is the only left-sidebar builder with a composite subtitle. Tests three branches of its inline buildSubtitle helper: - period + website.label → joined with ' • ' - period only → just the period - empty period + whitespace-only website.label → returns undefined * test(web): cover Education + Experience section builders - Education: school → title, degree → subtitle, add-new affordance - Experience: position → subtitle when set; falls back to '1 role' / 'N roles' (lingui plural) when position empty and roles[] present; add-new affordance * test(web): cover CountUp animated number renderer - default aria attributes (aria-live=polite, aria-atomic=true) - initial textContent seeds to 'from' (up) or 'to' (down) value - separator option formats with grouping - decimal places are preserved when from/to are fractional - aria-hidden=true strips aria-live + aria-atomic - custom className is applied to the rendered span * test(web): cover TextMaskEffect SVG renderer - supplied text renders in every visible layer - aria-hidden + aria-label forwarded onto the root svg - mouse enter/move/leave handlers don't throw - custom className merges into the svg's class attribute * test(web): cover URLInput prefix handling - displayed input strips the https:// prefix so users only edit the meaningful portion - editing re-adds the prefix on the way back through onChange - pre-prefixed input is preserved - cleared input emits an empty url (no prefix forced) - hideLabelButton=true removes the popover trigger; default keeps it * test(web): cover GithubStarsButton Mocks useQuery + the CountUp animation so the button renders standalone. Asserts: - anchor points at the project repo with rel=noopener + target=_blank - aria-label is the no-count copy when star count is undefined - CountUp renders only once the count loads - aria-label includes the localized count once data arrives * test(web): cover ui/Combobox trigger label rendering The shared Combobox wraps base-ui's combobox primitives. Smoke-test the trigger label resolution: - placeholder shows when nothing is selected - selected option's label renders in the trigger - multi-select default values render all labels - empty options array renders the placeholder without crashing * test(web): cover IconPicker trigger rendering Stub react-window's Grid so happy-dom can render the picker without layout-measurement deps. Verify: - trigger renders an for the current value - changing value updates the trigger icon class - the picker emits a trigger button * test(web): cover ChipInput add/dedupe/description behaviors - existing chips render as Badges - Enter adds the typed value to the chip list - comma also commits the typed value - duplicate input is dropped (onChange not called with a longer list) - empty / whitespace-only input is dropped - hideDescription removes the keyboard hint ; default keeps it * test(web): cover StatisticsSectionBuilder Mock useQuery + useParams + section-base so the right-sidebar statistics section renders standalone: - returns null content while the query is loading - shows the private-resume hint when isPublic=false - shows views/downloads counters and labels when isPublic=true - includes 'Last viewed' timestamp copy when lastViewedAt is set * test(web): cover TemplateGalleryDialog selection flow - title + intro copy render - one tile per template (>= 14) - currently-selected template tile carries the ring-highlight - clicking a different tile triggers updateResumeData with a recipe that sets metadata.template to the chosen template id * test(web): cover Prefooter home-page section - community tagline heading renders - community-thanks paragraph renders - the decorative TextMaskEffect renders an svg * test(web): cover home-page Footer - Resources and Community headings render - documented resource links (Documentation, Sponsorships, Source Code, Changelog) all appear in the rendered output - documented community links (Report an issue, Translations, Subreddit, Discord) all appear - social anchors point at GitHub, LinkedIn, and X (Twitter) - Copyright sub-component surfaces the app version via __APP_VERSION__ * test(web): cover home-page Header navigation Mock TanStack Router Link + child components so the header renders standalone. Verify: - homepage anchor (/ link) carries the documented aria-label - dashboard anchor points to /dashboard - ThemeToggleButton and GithubStarsButton both mount - the