@carlonicora/nextjs-jsonapi 1.17.0 → 1.18.0

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.
Files changed (109) hide show
  1. package/README.md +1 -1
  2. package/dist/{BlockNoteEditor-4Z6TZBJE.mjs → BlockNoteEditor-6TWTNHNZ.mjs} +2 -2
  3. package/dist/{BlockNoteEditor-34T5CY27.js → BlockNoteEditor-C3WWGGT6.js} +6 -6
  4. package/dist/{BlockNoteEditor-34T5CY27.js.map → BlockNoteEditor-C3WWGGT6.js.map} +1 -1
  5. package/dist/{chunk-D7H7SRWB.js → chunk-6U6QCSJK.js} +4054 -2590
  6. package/dist/chunk-6U6QCSJK.js.map +1 -0
  7. package/dist/{chunk-SXPXC2TY.mjs → chunk-UZDAPWJG.mjs} +5629 -4165
  8. package/dist/chunk-UZDAPWJG.mjs.map +1 -0
  9. package/dist/client/index.js +2 -2
  10. package/dist/client/index.mjs +1 -1
  11. package/dist/components/index.d.mts +298 -385
  12. package/dist/components/index.d.ts +298 -385
  13. package/dist/components/index.js +26 -2
  14. package/dist/components/index.js.map +1 -1
  15. package/dist/components/index.mjs +35 -11
  16. package/dist/contexts/index.js +2 -2
  17. package/dist/contexts/index.mjs +1 -1
  18. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -1
  19. package/dist/scripts/generate-web-module/templates/components/editor.template.js +20 -6
  20. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
  21. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
  22. package/dist/scripts/generate-web-module/templates/components/selector.template.js +45 -48
  23. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
  24. package/dist/testing/index.js.map +1 -1
  25. package/dist/testing/index.mjs.map +1 -1
  26. package/package.json +6 -25
  27. package/scripts/generate-web-module/templates/components/editor.template.ts +20 -6
  28. package/scripts/generate-web-module/templates/components/selector.template.ts +45 -48
  29. package/src/components/forms/CommonDeleter.tsx +2 -2
  30. package/src/components/forms/CommonEditorTrigger.tsx +3 -3
  31. package/src/components/forms/DatePickerPopover.tsx +3 -1
  32. package/src/components/forms/DateRangeSelector.tsx +1 -1
  33. package/src/components/forms/FormCheckbox.tsx +1 -1
  34. package/src/components/forms/FormDate.tsx +3 -1
  35. package/src/components/forms/FormDateTime.tsx +5 -3
  36. package/src/components/forms/FormSelect.tsx +1 -1
  37. package/src/components/forms/FormSlider.tsx +4 -1
  38. package/src/components/forms/__tests__/FormCheckbox.test.tsx +5 -1
  39. package/src/components/forms/__tests__/FormDate.test.tsx +5 -1
  40. package/src/components/forms/__tests__/FormSelect.test.tsx +5 -1
  41. package/src/components/navigations/RecentPagesNavigator.tsx +2 -2
  42. package/src/components/tables/ContentListTable.tsx +3 -3
  43. package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +2 -2
  44. package/src/features/company/components/forms/CompanyConfigurationEditor.tsx +2 -2
  45. package/src/features/company/components/forms/CompanyDeleter.tsx +1 -1
  46. package/src/features/content/components/lists/ContentsList.tsx +1 -1
  47. package/src/features/notification/components/lists/NotificationsList.tsx +1 -1
  48. package/src/features/notification/components/modals/NotificationModal.tsx +2 -2
  49. package/src/features/role/components/forms/FormRoles.tsx +1 -1
  50. package/src/features/user/components/forms/UserEditor.tsx +2 -2
  51. package/src/features/user/components/forms/UserReactivator.tsx +1 -1
  52. package/src/features/user/components/forms/UserResentInvitationEmail.tsx +2 -2
  53. package/src/features/user/components/widgets/UserAvatar.tsx +37 -31
  54. package/src/features/user/components/widgets/UserSearchPopover.tsx +1 -1
  55. package/src/hooks/use-mobile.ts +1 -0
  56. package/src/lib/utils.ts +2 -0
  57. package/src/shadcnui/custom/multi-select.tsx +10 -21
  58. package/src/shadcnui/ui/accordion.tsx +64 -42
  59. package/src/shadcnui/ui/alert-dialog.tsx +142 -108
  60. package/src/shadcnui/ui/alert.tsx +64 -35
  61. package/src/shadcnui/ui/avatar.tsx +106 -50
  62. package/src/shadcnui/ui/badge.tsx +34 -26
  63. package/src/shadcnui/ui/breadcrumb.tsx +103 -92
  64. package/src/shadcnui/ui/button.tsx +30 -30
  65. package/src/shadcnui/ui/calendar.tsx +192 -50
  66. package/src/shadcnui/ui/card.tsx +94 -43
  67. package/src/shadcnui/ui/carousel.tsx +220 -201
  68. package/src/shadcnui/ui/chart.tsx +244 -190
  69. package/src/shadcnui/ui/checkbox.tsx +25 -25
  70. package/src/shadcnui/ui/collapsible.tsx +10 -4
  71. package/src/shadcnui/ui/combobox.tsx +292 -0
  72. package/src/shadcnui/ui/command.tsx +158 -126
  73. package/src/shadcnui/ui/context-menu.tsx +242 -164
  74. package/src/shadcnui/ui/dialog.tsx +125 -70
  75. package/src/shadcnui/ui/drawer.tsx +106 -70
  76. package/src/shadcnui/ui/dropdown-menu.tsx +231 -182
  77. package/src/shadcnui/ui/field.tsx +227 -0
  78. package/src/shadcnui/ui/hover-card.tsx +45 -23
  79. package/src/shadcnui/ui/input-group.tsx +149 -0
  80. package/src/shadcnui/ui/input-otp.tsx +19 -9
  81. package/src/shadcnui/ui/input.tsx +4 -5
  82. package/src/shadcnui/ui/label.tsx +16 -22
  83. package/src/shadcnui/ui/navigation-menu.tsx +44 -49
  84. package/src/shadcnui/ui/popover.tsx +81 -24
  85. package/src/shadcnui/ui/progress.tsx +77 -22
  86. package/src/shadcnui/ui/radio-group.tsx +30 -28
  87. package/src/shadcnui/ui/resizable.tsx +23 -17
  88. package/src/shadcnui/ui/scroll-area.tsx +50 -35
  89. package/src/shadcnui/ui/select.tsx +163 -135
  90. package/src/shadcnui/ui/separator.tsx +5 -8
  91. package/src/shadcnui/ui/sheet.tsx +40 -50
  92. package/src/shadcnui/ui/sidebar.tsx +317 -271
  93. package/src/shadcnui/ui/skeleton.tsx +2 -2
  94. package/src/shadcnui/ui/slider.tsx +60 -21
  95. package/src/shadcnui/ui/sonner.tsx +25 -1
  96. package/src/shadcnui/ui/switch.tsx +31 -24
  97. package/src/shadcnui/ui/table.tsx +84 -103
  98. package/src/shadcnui/ui/tabs.tsx +82 -55
  99. package/src/shadcnui/ui/textarea.tsx +15 -21
  100. package/src/shadcnui/ui/toggle.tsx +26 -21
  101. package/src/shadcnui/ui/tooltip.tsx +33 -24
  102. package/src/testing/factories/createMockApiData.ts +2 -2
  103. package/src/testing/factories/createMockResponse.ts +3 -8
  104. package/src/testing/factories/createMockService.ts +1 -4
  105. package/src/testing/index.ts +4 -18
  106. package/src/testing/matchers/jsonApiMatchers.ts +14 -16
  107. package/dist/chunk-D7H7SRWB.js.map +0 -1
  108. package/dist/chunk-SXPXC2TY.mjs.map +0 -1
  109. /package/dist/{BlockNoteEditor-4Z6TZBJE.mjs.map → BlockNoteEditor-6TWTNHNZ.mjs.map} +0 -0
@@ -17,7 +17,11 @@ function generateSelectorTemplate(data) {
17
17
  return `"use client";
18
18
 
19
19
  import {
20
+ Button,
20
21
  Command,
22
+ CommandEmpty,
23
+ CommandGroup,
24
+ CommandInput,
21
25
  CommandItem,
22
26
  CommandList,
23
27
  FormControl,
@@ -25,7 +29,6 @@ import {
25
29
  FormItem,
26
30
  FormLabel,
27
31
  FormMessage,
28
- Input,
29
32
  Popover,
30
33
  PopoverContent,
31
34
  PopoverTrigger,
@@ -36,7 +39,7 @@ import { DataListRetriever, useDataListRetriever } from "@carlonicora/nextjs-jso
36
39
  import { useDebounce } from "@carlonicora/nextjs-jsonapi/client";
37
40
  import { Modules } from "@carlonicora/nextjs-jsonapi/core";
38
41
 
39
- import { CircleX, RefreshCwIcon, SearchIcon, XIcon } from "lucide-react";
42
+ import { ChevronsUpDown, Loader2, XIcon } from "lucide-react";
40
43
  import { useTranslations } from "next-intl";
41
44
  import { useCallback, useEffect, useRef, useState } from "react";
42
45
 
@@ -128,60 +131,54 @@ export default function ${names.pascalCase}Selector({
128
131
  <Popover open={open} onOpenChange={setOpen} modal={true}>
129
132
  <div className="flex w-full flex-row items-center justify-between">
130
133
  <PopoverTrigger className="w-full">
131
- <div className="flex w-full flex-row items-center justify-start rounded-md text-sm">
132
- {field.value ? (
133
- <>
134
- <div className="flex w-full flex-row items-center justify-start rounded-md border p-2">
135
- <span className="">{field.value?.name ?? ""}</span>
136
- </div>
137
- </>
138
- ) : (
139
- <div className="text-muted-foreground mr-7 flex h-10 w-full flex-row items-center justify-start rounded-md border p-2 text-sm">
140
- {placeholder ?? t(\`generic.search.placeholder\`, { type: t(\`types.${names.pluralKebab}\`, { count: 1 }) })}
141
- </div>
142
- )}
143
- </div>
134
+ <Button
135
+ variant="outline"
136
+ role="combobox"
137
+ aria-expanded={open}
138
+ className="h-auto min-h-10 w-full justify-between px-3 py-2"
139
+ >
140
+ <span className={field.value ? "" : "text-muted-foreground"}>
141
+ {field.value?.name ?? placeholder ?? t(\`generic.search.placeholder\`, { type: t(\`types.${names.pluralKebab}\`, { count: 1 }) })}
142
+ </span>
143
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
144
+ </Button>
144
145
  </PopoverTrigger>
145
146
  {field.value && (
146
- <CircleX
147
- className="text-muted hover:text-destructive ml-2 h-6 w-6 cursor-pointer"
147
+ <XIcon
148
+ className="text-muted-foreground hover:text-destructive ml-2 h-5 w-5 cursor-pointer"
148
149
  onClick={() => set${names.pascalCase}()}
149
150
  />
150
151
  )}
151
152
  </div>
152
- <PopoverContent>
153
+ <PopoverContent className="w-(--radix-popover-trigger-width) p-0" align="start">
153
154
  <Command shouldFilter={false}>
154
- <div className="relative mb-2 w-full">
155
- <SearchIcon className="text-muted-foreground absolute left-2.5 top-2.5 h-4 w-4" />
156
- <Input
157
- placeholder={t(\`generic.search.placeholder\`, { type: t(\`types.${names.pluralKebab}\`, { count: 1 }) })}
158
- type="text"
159
- className="w-full pl-8 pr-8"
160
- onChange={(e) => setSearchTerm(e.target.value)}
161
- value={searchTerm}
162
- />
163
- {isSearching ? (
164
- <RefreshCwIcon className="text-muted-foreground absolute right-2.5 top-2.5 h-4 w-4 animate-spin" />
165
- ) : searchTermRef.current ? (
166
- <XIcon
167
- className={\`absolute right-2.5 top-2.5 h-4 w-4 \${searchTermRef.current ? "cursor-pointer" : "text-muted-foreground"}\`}
168
- onClick={() => {
169
- setSearchTerm("");
170
- search("");
171
- }}
172
- />
173
- ) : (
174
- <></>
175
- )}
176
- </div>
155
+ <CommandInput
156
+ placeholder={t(\`generic.search.placeholder\`, { type: t(\`types.${names.pluralKebab}\`, { count: 1 }) })}
157
+ value={searchTerm}
158
+ onValueChange={setSearchTerm}
159
+ />
177
160
  <CommandList>
178
- {data.data &&
179
- data.data.length > 0 &&
180
- (data.data as ${names.pascalCase}Interface[]).map((${names.camelCase}: ${names.pascalCase}Interface) => (
181
- <CommandItem className="cursor-pointer" key={${names.camelCase}.id} onSelect={() => set${names.pascalCase}(${names.camelCase})}>
182
- <span className="">{${names.camelCase}.name}</span>
183
- </CommandItem>
184
- ))}
161
+ {isSearching && (
162
+ <div className="flex items-center justify-center py-6">
163
+ <Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
164
+ </div>
165
+ )}
166
+ {!isSearching && data.data && data.data.length === 0 && (
167
+ <CommandEmpty>{t(\`generic.search.no_results\`)}</CommandEmpty>
168
+ )}
169
+ {!isSearching && data.data && data.data.length > 0 && (
170
+ <CommandGroup>
171
+ {(data.data as ${names.pascalCase}Interface[]).map((${names.camelCase}: ${names.pascalCase}Interface) => (
172
+ <CommandItem
173
+ key={${names.camelCase}.id}
174
+ value={${names.camelCase}.id}
175
+ onSelect={() => set${names.pascalCase}(${names.camelCase})}
176
+ >
177
+ {${names.camelCase}.name}
178
+ </CommandItem>
179
+ ))}
180
+ </CommandGroup>
181
+ )}
185
182
  </CommandList>
186
183
  </Command>
187
184
  </PopoverContent>
@@ -1 +1 @@
1
- {"version":3,"file":"selector.template.js","sourceRoot":"","sources":["../../../../../scripts/generate-web-module/templates/components/selector.template.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAUH,4DAwLC;AA9LD;;;;;GAKG;AACH,SAAgB,wBAAwB,CAAC,IAA0B;IACjE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAEvB,OAAO;;;;;;;;;;;;;;;;WAgBE,KAAK,CAAC,UAAU,gCAAgC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,SAAS,KAAK,CAAC,UAAU;WAC1G,KAAK,CAAC,UAAU,8BAA8B,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,SAAS,KAAK,CAAC,UAAU;;;;;;;;;OAS5G,KAAK,CAAC,UAAU;;;;;gBAKP,KAAK,CAAC,SAAS,MAAM,KAAK,CAAC,UAAU;;;;0BAI3B,KAAK,CAAC,UAAU;;;;;;;KAOrC,KAAK,CAAC,UAAU;;;;;;;;;;kCAUa,KAAK,CAAC,UAAU;;eAEnC,KAAK,CAAC,UAAU;;;sBAGT,KAAK,CAAC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;aAwBzB,KAAK,CAAC,UAAU,OAAO,KAAK,CAAC,SAAS,MAAM,KAAK,CAAC,UAAU;6BAC5C,KAAK,CAAC,SAAS;WACjC,KAAK,CAAC,SAAS;;;;;;8BAMI,KAAK,CAAC,SAAS,cAAc,KAAK,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gGAkCsB,KAAK,CAAC,WAAW;;;;;;;;0CAQvE,KAAK,CAAC,UAAU;;;;;;;;;2FASiC,KAAK,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;wCAuBpE,KAAK,CAAC,UAAU,qBAAqB,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,UAAU;yEACxC,KAAK,CAAC,SAAS,2BAA2B,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS;kDACpG,KAAK,CAAC,SAAS;;;;;;;;;;;;;;;CAehE,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"selector.template.js","sourceRoot":"","sources":["../../../../../scripts/generate-web-module/templates/components/selector.template.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAUH,4DAqLC;AA3LD;;;;;GAKG;AACH,SAAgB,wBAAwB,CAAC,IAA0B;IACjE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAEvB,OAAO;;;;;;;;;;;;;;;;;;;WAmBE,KAAK,CAAC,UAAU,gCAAgC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,SAAS,KAAK,CAAC,UAAU;WAC1G,KAAK,CAAC,UAAU,8BAA8B,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,SAAS,KAAK,CAAC,UAAU;;;;;;;;;OAS5G,KAAK,CAAC,UAAU;;;;;gBAKP,KAAK,CAAC,SAAS,MAAM,KAAK,CAAC,UAAU;;;;0BAI3B,KAAK,CAAC,UAAU;;;;;;;KAOrC,KAAK,CAAC,UAAU;;;;;;;;;;kCAUa,KAAK,CAAC,UAAU;;eAEnC,KAAK,CAAC,UAAU;;;sBAGT,KAAK,CAAC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;aAwBzB,KAAK,CAAC,UAAU,OAAO,KAAK,CAAC,SAAS,MAAM,KAAK,CAAC,UAAU;6BAC5C,KAAK,CAAC,SAAS;WACjC,KAAK,CAAC,SAAS;;;;;;8BAMI,KAAK,CAAC,SAAS,cAAc,KAAK,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mHAgCyC,KAAK,CAAC,WAAW;;;;;;;;0CAQ1F,KAAK,CAAC,UAAU;;;;;;;yFAO+B,KAAK,CAAC,WAAW;;;;;;;;;;;;;;;2CAe/D,KAAK,CAAC,UAAU,qBAAqB,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,UAAU;;qCAE/E,KAAK,CAAC,SAAS;uCACb,KAAK,CAAC,SAAS;mDACH,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS;;iCAErD,KAAK,CAAC,SAAS;;;;;;;;;;;;;;;;;CAiB/C,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","../../src/testing/providers/MockJsonApiProvider.tsx","../../src/testing/factories/createMockModule.ts","../../src/testing/factories/createMockResponse.ts","../../src/testing/factories/createMockService.ts","../../src/testing/factories/createMockApiData.ts","../../src/testing/matchers/jsonApiMatchers.ts","../../src/testing/utils/renderWithProviders.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","sourcesContent":[null,"\"use client\";\n\nimport React from \"react\";\nimport { JsonApiConfig, JsonApiContext } from \"../../client/context/JsonApiContext\";\n\nexport interface MockJsonApiProviderProps {\n children: React.ReactNode;\n config?: Partial<JsonApiConfig>;\n}\n\nconst defaultMockConfig: JsonApiConfig = {\n apiUrl: \"https://api.test.com\",\n tokenGetter: async () => \"mock-token-for-testing\",\n languageGetter: async () => \"en\",\n defaultHeaders: {},\n onError: () => {},\n cacheConfig: {\n defaultProfile: \"default\",\n },\n};\n\n/**\n * A test-friendly provider that wraps components with mock JSON:API context.\n *\n * @example\n * ```tsx\n * import { MockJsonApiProvider } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * render(\n * <MockJsonApiProvider>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n *\n * @example With custom config\n * ```tsx\n * render(\n * <MockJsonApiProvider config={{ apiUrl: 'https://custom.api.com' }}>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n */\nexport function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps) {\n const mergedConfig: JsonApiConfig = {\n ...defaultMockConfig,\n ...config,\n };\n\n return (\n <JsonApiContext.Provider value={mergedConfig}>\n {children}\n </JsonApiContext.Provider>\n );\n}\n\nexport { defaultMockConfig };\n","import { ApiRequestDataTypeInterface } from \"../../core/interfaces/ApiRequestDataTypeInterface\";\n\nexport interface CreateMockModuleOptions {\n name: string;\n cache?: string;\n inclusions?: ApiRequestDataTypeInterface[\"inclusions\"];\n}\n\n/**\n * Creates a mock module definition for testing.\n *\n * @example\n * ```ts\n * import { createMockModule } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticleModule = createMockModule({ name: 'articles' });\n * ```\n */\nexport function createMockModule(options: CreateMockModuleOptions): ApiRequestDataTypeInterface {\n // Create a mock model class\n class MockModel {\n id = \"mock-id\";\n type = options.name;\n }\n\n return {\n name: options.name,\n cache: options.cache,\n inclusions: options.inclusions,\n model: MockModel,\n };\n}\n","import { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockResponseOptions {\n data?: ApiDataInterface | ApiDataInterface[] | null;\n ok?: boolean;\n response?: number;\n error?: string;\n meta?: Record<string, any>;\n self?: string;\n next?: string;\n prev?: string;\n}\n\n/**\n * Creates a mock API response for testing.\n *\n * @example\n * ```ts\n * import { createMockResponse, createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockData = createMockApiData({ type: 'articles', id: '1' });\n * const response = createMockResponse({ data: mockData, ok: true });\n * ```\n *\n * @example With pagination\n * ```ts\n * const response = createMockResponse({\n * data: [mockData],\n * ok: true,\n * next: '/articles?page=2',\n * prev: '/articles?page=0',\n * });\n * ```\n */\nexport function createMockResponse(options: CreateMockResponseOptions = {}): ApiResponseInterface {\n const {\n data = null,\n ok = true,\n response = ok ? 200 : 500,\n error = ok ? \"\" : \"Error\",\n meta,\n self,\n next,\n prev,\n } = options;\n\n const mockResponse: ApiResponseInterface = {\n ok,\n response,\n data: data as ApiDataInterface | ApiDataInterface[],\n error,\n meta,\n self,\n next,\n prev,\n };\n\n // Add pagination methods if next/prev provided\n if (next) {\n mockResponse.nextPage = async () =>\n createMockResponse({ ...options, next: undefined, prev: self });\n }\n\n if (prev) {\n mockResponse.prevPage = async () =>\n createMockResponse({ ...options, prev: undefined, next: self });\n }\n\n return mockResponse;\n}\n\n/**\n * Creates a mock error response for testing error scenarios.\n *\n * @example\n * ```ts\n * import { createMockErrorResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorResponse = createMockErrorResponse(404, 'Not Found');\n * ```\n */\nexport function createMockErrorResponse(\n statusCode: number,\n errorMessage: string\n): ApiResponseInterface {\n return createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n data: null,\n });\n}\n","import { vi, type Mock } from \"vitest\";\nimport { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { createMockResponse } from \"./createMockResponse\";\n\nexport type MockApiMethod = Mock<(...args: any[]) => Promise<ApiResponseInterface>>;\n\nexport interface MockService {\n get: MockApiMethod;\n post: MockApiMethod;\n put: MockApiMethod;\n patch: MockApiMethod;\n delete: MockApiMethod;\n}\n\nexport interface CreateMockServiceOptions {\n defaultResponse?: ApiResponseInterface;\n}\n\n/**\n * Creates a mock service with Vitest mock functions for all HTTP methods.\n *\n * @example\n * ```ts\n * import { createMockService, createMockResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockService = createMockService();\n * mockService.get.mockResolvedValue(createMockResponse({ data: mockData }));\n *\n * // Use in test\n * expect(mockService.get).toHaveBeenCalled();\n * ```\n *\n * @example With default response\n * ```ts\n * const mockService = createMockService({\n * defaultResponse: createMockResponse({ ok: true, data: [] }),\n * });\n * ```\n */\nexport function createMockService(options: CreateMockServiceOptions = {}): MockService {\n const defaultResponse = options.defaultResponse ?? createMockResponse({ ok: true });\n\n return {\n get: vi.fn().mockResolvedValue(defaultResponse),\n post: vi.fn().mockResolvedValue(defaultResponse),\n put: vi.fn().mockResolvedValue(defaultResponse),\n patch: vi.fn().mockResolvedValue(defaultResponse),\n delete: vi.fn().mockResolvedValue(defaultResponse),\n };\n}\n\n/**\n * Creates a mock service that returns errors for all methods.\n *\n * @example\n * ```ts\n * import { createMockErrorService } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorService = createMockErrorService(500, 'Internal Server Error');\n * ```\n */\nexport function createMockErrorService(\n statusCode: number = 500,\n errorMessage: string = \"Error\"\n): MockService {\n const errorResponse = createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n });\n\n return {\n get: vi.fn().mockResolvedValue(errorResponse),\n post: vi.fn().mockResolvedValue(errorResponse),\n put: vi.fn().mockResolvedValue(errorResponse),\n patch: vi.fn().mockResolvedValue(errorResponse),\n delete: vi.fn().mockResolvedValue(errorResponse),\n };\n}\n","import { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockApiDataOptions {\n type: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n included?: any[];\n createdAt?: Date;\n updatedAt?: Date;\n self?: string;\n}\n\n/**\n * Creates a mock ApiDataInterface object for testing.\n *\n * @example\n * ```ts\n * import { createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test Article', body: 'Content here' },\n * });\n * ```\n *\n * @example With relationships\n * ```ts\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test' },\n * relationships: {\n * author: { data: { type: 'users', id: '42' } },\n * },\n * });\n * ```\n */\nexport function createMockApiData(options: CreateMockApiDataOptions): ApiDataInterface {\n const {\n type,\n id = `mock-${type}-${Math.random().toString(36).substring(7)}`,\n attributes = {},\n relationships = {},\n included = [],\n createdAt = new Date(),\n updatedAt = new Date(),\n self,\n } = options;\n\n const jsonApiData = {\n type,\n id,\n attributes: {\n ...attributes,\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n },\n relationships,\n };\n\n const mockData: ApiDataInterface = {\n get included() {\n return included;\n },\n get type() {\n return type;\n },\n get id() {\n return id;\n },\n get createdAt() {\n return createdAt;\n },\n get updatedAt() {\n return updatedAt;\n },\n get self() {\n return self;\n },\n get jsonApi() {\n return jsonApiData;\n },\n generateApiUrl: (params?: any) => {\n const baseUrl = `/${type}/${id}`;\n if (params) {\n const searchParams = new URLSearchParams(params);\n return `${baseUrl}?${searchParams.toString()}`;\n }\n return baseUrl;\n },\n dehydrate: () => ({\n jsonApi: jsonApiData,\n included,\n allData: [jsonApiData],\n }),\n rehydrate: function (data: any) {\n return this;\n },\n createJsonApi: (data: any) => ({\n type,\n id,\n attributes: data,\n }),\n };\n\n // Add attribute accessors to the mock object\n Object.keys(attributes).forEach((key) => {\n Object.defineProperty(mockData, key, {\n get: () => attributes[key],\n enumerable: true,\n });\n });\n\n return mockData;\n}\n\n/**\n * Creates an array of mock ApiDataInterface objects.\n *\n * @example\n * ```ts\n * import { createMockApiDataList } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticles = createMockApiDataList('articles', 5, (index) => ({\n * title: `Article ${index + 1}`,\n * }));\n * ```\n */\nexport function createMockApiDataList(\n type: string,\n count: number,\n attributesFactory?: (index: number) => Record<string, any>\n): ApiDataInterface[] {\n return Array.from({ length: count }, (_, index) =>\n createMockApiData({\n type,\n id: `${index + 1}`,\n attributes: attributesFactory?.(index) ?? {},\n })\n );\n}\n","import { expect } from \"vitest\";\n\ninterface JsonApiResponse {\n data?: {\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n } | Array<{\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }>;\n}\n\n/**\n * Custom Vitest matchers for JSON:API assertions.\n *\n * @example\n * ```ts\n * import { jsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n * import { expect } from 'vitest';\n *\n * expect.extend(jsonApiMatchers);\n *\n * // Then use in tests:\n * expect(response).toBeValidJsonApi();\n * expect(response).toHaveJsonApiType('articles');\n * expect(response).toHaveJsonApiAttribute('title', 'My Article');\n * expect(response).toHaveJsonApiRelationship('author');\n * ```\n */\nexport const jsonApiMatchers = {\n /**\n * Asserts that the response has a valid JSON:API structure with type and id.\n */\n toBeValidJsonApi(received: JsonApiResponse) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const hasType = typeof data?.type === \"string\" && data.type.length > 0;\n const hasId = typeof data?.id === \"string\" && data.id.length > 0;\n const isValid = hasType && hasId;\n\n return {\n pass: isValid,\n message: () =>\n isValid\n ? `Expected response not to be valid JSON:API, but it has type \"${data?.type}\" and id \"${data?.id}\"`\n : `Expected response to be valid JSON:API with type and id, but got type: ${JSON.stringify(data?.type)}, id: ${JSON.stringify(data?.id)}`,\n };\n },\n\n /**\n * Asserts that the response data has the expected JSON:API type.\n */\n toHaveJsonApiType(received: JsonApiResponse, expectedType: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const actualType = data?.type;\n const pass = actualType === expectedType;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have JSON:API type \"${expectedType}\"`\n : `Expected response to have JSON:API type \"${expectedType}\", but got \"${actualType}\"`,\n };\n },\n\n /**\n * Asserts that the response data has an attribute with the expected value.\n */\n toHaveJsonApiAttribute(\n received: JsonApiResponse,\n attributeName: string,\n expectedValue?: any\n ) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const attributes = data?.attributes ?? {};\n const hasAttribute = attributeName in attributes;\n const actualValue = attributes[attributeName];\n\n if (expectedValue === undefined) {\n // Just check existence\n return {\n pass: hasAttribute,\n message: () =>\n hasAttribute\n ? `Expected response not to have JSON:API attribute \"${attributeName}\"`\n : `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found. Available attributes: ${Object.keys(attributes).join(\", \") || \"none\"}`,\n };\n }\n\n const valuesMatch = actualValue === expectedValue;\n return {\n pass: hasAttribute && valuesMatch,\n message: () =>\n hasAttribute && valuesMatch\n ? `Expected response not to have JSON:API attribute \"${attributeName}\" with value \"${expectedValue}\"`\n : !hasAttribute\n ? `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found`\n : `Expected JSON:API attribute \"${attributeName}\" to be \"${expectedValue}\", but got \"${actualValue}\"`,\n };\n },\n\n /**\n * Asserts that the response data has the specified relationship.\n */\n toHaveJsonApiRelationship(received: JsonApiResponse, relationshipName: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const relationships = data?.relationships ?? {};\n const hasRelationship = relationshipName in relationships;\n\n return {\n pass: hasRelationship,\n message: () =>\n hasRelationship\n ? `Expected response not to have JSON:API relationship \"${relationshipName}\"`\n : `Expected response to have JSON:API relationship \"${relationshipName}\", but it was not found. Available relationships: ${Object.keys(relationships).join(\", \") || \"none\"}`,\n };\n },\n\n /**\n * Asserts that the response data array has the expected length.\n */\n toHaveJsonApiLength(received: JsonApiResponse, expectedLength: number) {\n const data = received?.data;\n const isArray = Array.isArray(data);\n const actualLength = isArray ? data.length : data ? 1 : 0;\n const pass = actualLength === expectedLength;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have ${expectedLength} items`\n : `Expected response to have ${expectedLength} items, but got ${actualLength}`,\n };\n },\n};\n\n// Type declarations for the custom matchers\ndeclare module \"vitest\" {\n interface Assertion<T = any> {\n toBeValidJsonApi(): T;\n toHaveJsonApiType(expectedType: string): T;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): T;\n toHaveJsonApiRelationship(relationshipName: string): T;\n toHaveJsonApiLength(expectedLength: number): T;\n }\n interface AsymmetricMatchersContaining {\n toBeValidJsonApi(): any;\n toHaveJsonApiType(expectedType: string): any;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;\n toHaveJsonApiRelationship(relationshipName: string): any;\n toHaveJsonApiLength(expectedLength: number): any;\n }\n}\n\n/**\n * Extends Vitest's expect with JSON:API matchers.\n * Call this in your test setup file.\n *\n * @example\n * ```ts\n * // vitest.setup.ts\n * import { extendExpectWithJsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * extendExpectWithJsonApiMatchers();\n * ```\n */\nexport function extendExpectWithJsonApiMatchers(): void {\n expect.extend(jsonApiMatchers);\n}\n","\"use client\";\n\nimport React, { ReactElement } from \"react\";\nimport { render, RenderOptions, RenderResult } from \"@testing-library/react\";\nimport { MockJsonApiProvider, MockJsonApiProviderProps } from \"../providers/MockJsonApiProvider\";\nimport { JsonApiConfig } from \"../../client/context/JsonApiContext\";\n\nexport interface RenderWithProvidersOptions extends Omit<RenderOptions, \"wrapper\"> {\n /**\n * Custom JSON:API configuration to pass to the mock provider.\n */\n jsonApiConfig?: Partial<JsonApiConfig>;\n\n /**\n * Additional wrapper component to wrap around the providers.\n */\n wrapper?: React.ComponentType<{ children: React.ReactNode }>;\n}\n\n/**\n * Renders a component wrapped with all necessary providers for testing.\n *\n * @example\n * ```tsx\n * import { renderWithProviders } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const { getByText } = renderWithProviders(<MyComponent />);\n * expect(getByText('Hello')).toBeInTheDocument();\n * ```\n *\n * @example With custom config\n * ```tsx\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * jsonApiConfig: { apiUrl: 'https://custom.api.com' },\n * });\n * ```\n *\n * @example With additional wrapper\n * ```tsx\n * const CustomWrapper = ({ children }) => (\n * <ThemeProvider>{children}</ThemeProvider>\n * );\n *\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * wrapper: CustomWrapper,\n * });\n * ```\n */\nexport function renderWithProviders(\n ui: ReactElement,\n options: RenderWithProvidersOptions = {}\n): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = (\n <MockJsonApiProvider config={jsonApiConfig}>\n {children}\n </MockJsonApiProvider>\n );\n\n if (AdditionalWrapper) {\n return <AdditionalWrapper>{content}</AdditionalWrapper>;\n }\n\n return content;\n }\n\n return render(ui, { wrapper: AllProviders, ...renderOptions });\n}\n\n/**\n * Re-export render utilities from Testing Library for convenience.\n */\nexport { render, screen, waitFor, fireEvent, within } from \"@testing-library/react\";\nexport { userEvent } from \"@testing-library/user-event\";\n"]}
1
+ {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","../../src/testing/providers/MockJsonApiProvider.tsx","../../src/testing/factories/createMockModule.ts","../../src/testing/factories/createMockResponse.ts","../../src/testing/factories/createMockService.ts","../../src/testing/factories/createMockApiData.ts","../../src/testing/matchers/jsonApiMatchers.ts","../../src/testing/utils/renderWithProviders.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","sourcesContent":[null,"\"use client\";\n\nimport React from \"react\";\nimport { JsonApiConfig, JsonApiContext } from \"../../client/context/JsonApiContext\";\n\nexport interface MockJsonApiProviderProps {\n children: React.ReactNode;\n config?: Partial<JsonApiConfig>;\n}\n\nconst defaultMockConfig: JsonApiConfig = {\n apiUrl: \"https://api.test.com\",\n tokenGetter: async () => \"mock-token-for-testing\",\n languageGetter: async () => \"en\",\n defaultHeaders: {},\n onError: () => {},\n cacheConfig: {\n defaultProfile: \"default\",\n },\n};\n\n/**\n * A test-friendly provider that wraps components with mock JSON:API context.\n *\n * @example\n * ```tsx\n * import { MockJsonApiProvider } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * render(\n * <MockJsonApiProvider>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n *\n * @example With custom config\n * ```tsx\n * render(\n * <MockJsonApiProvider config={{ apiUrl: 'https://custom.api.com' }}>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n */\nexport function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps) {\n const mergedConfig: JsonApiConfig = {\n ...defaultMockConfig,\n ...config,\n };\n\n return (\n <JsonApiContext.Provider value={mergedConfig}>\n {children}\n </JsonApiContext.Provider>\n );\n}\n\nexport { defaultMockConfig };\n","import { ApiRequestDataTypeInterface } from \"../../core/interfaces/ApiRequestDataTypeInterface\";\n\nexport interface CreateMockModuleOptions {\n name: string;\n cache?: string;\n inclusions?: ApiRequestDataTypeInterface[\"inclusions\"];\n}\n\n/**\n * Creates a mock module definition for testing.\n *\n * @example\n * ```ts\n * import { createMockModule } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticleModule = createMockModule({ name: 'articles' });\n * ```\n */\nexport function createMockModule(options: CreateMockModuleOptions): ApiRequestDataTypeInterface {\n // Create a mock model class\n class MockModel {\n id = \"mock-id\";\n type = options.name;\n }\n\n return {\n name: options.name,\n cache: options.cache,\n inclusions: options.inclusions,\n model: MockModel,\n };\n}\n","import { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockResponseOptions {\n data?: ApiDataInterface | ApiDataInterface[] | null;\n ok?: boolean;\n response?: number;\n error?: string;\n meta?: Record<string, any>;\n self?: string;\n next?: string;\n prev?: string;\n}\n\n/**\n * Creates a mock API response for testing.\n *\n * @example\n * ```ts\n * import { createMockResponse, createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockData = createMockApiData({ type: 'articles', id: '1' });\n * const response = createMockResponse({ data: mockData, ok: true });\n * ```\n *\n * @example With pagination\n * ```ts\n * const response = createMockResponse({\n * data: [mockData],\n * ok: true,\n * next: '/articles?page=2',\n * prev: '/articles?page=0',\n * });\n * ```\n */\nexport function createMockResponse(options: CreateMockResponseOptions = {}): ApiResponseInterface {\n const {\n data = null,\n ok = true,\n response = ok ? 200 : 500,\n error = ok ? \"\" : \"Error\",\n meta,\n self,\n next,\n prev,\n } = options;\n\n const mockResponse: ApiResponseInterface = {\n ok,\n response,\n data: data as ApiDataInterface | ApiDataInterface[],\n error,\n meta,\n self,\n next,\n prev,\n };\n\n // Add pagination methods if next/prev provided\n if (next) {\n mockResponse.nextPage = async () => createMockResponse({ ...options, next: undefined, prev: self });\n }\n\n if (prev) {\n mockResponse.prevPage = async () => createMockResponse({ ...options, prev: undefined, next: self });\n }\n\n return mockResponse;\n}\n\n/**\n * Creates a mock error response for testing error scenarios.\n *\n * @example\n * ```ts\n * import { createMockErrorResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorResponse = createMockErrorResponse(404, 'Not Found');\n * ```\n */\nexport function createMockErrorResponse(statusCode: number, errorMessage: string): ApiResponseInterface {\n return createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n data: null,\n });\n}\n","import { vi, type Mock } from \"vitest\";\nimport { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { createMockResponse } from \"./createMockResponse\";\n\nexport type MockApiMethod = Mock<(...args: any[]) => Promise<ApiResponseInterface>>;\n\nexport interface MockService {\n get: MockApiMethod;\n post: MockApiMethod;\n put: MockApiMethod;\n patch: MockApiMethod;\n delete: MockApiMethod;\n}\n\nexport interface CreateMockServiceOptions {\n defaultResponse?: ApiResponseInterface;\n}\n\n/**\n * Creates a mock service with Vitest mock functions for all HTTP methods.\n *\n * @example\n * ```ts\n * import { createMockService, createMockResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockService = createMockService();\n * mockService.get.mockResolvedValue(createMockResponse({ data: mockData }));\n *\n * // Use in test\n * expect(mockService.get).toHaveBeenCalled();\n * ```\n *\n * @example With default response\n * ```ts\n * const mockService = createMockService({\n * defaultResponse: createMockResponse({ ok: true, data: [] }),\n * });\n * ```\n */\nexport function createMockService(options: CreateMockServiceOptions = {}): MockService {\n const defaultResponse = options.defaultResponse ?? createMockResponse({ ok: true });\n\n return {\n get: vi.fn().mockResolvedValue(defaultResponse),\n post: vi.fn().mockResolvedValue(defaultResponse),\n put: vi.fn().mockResolvedValue(defaultResponse),\n patch: vi.fn().mockResolvedValue(defaultResponse),\n delete: vi.fn().mockResolvedValue(defaultResponse),\n };\n}\n\n/**\n * Creates a mock service that returns errors for all methods.\n *\n * @example\n * ```ts\n * import { createMockErrorService } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorService = createMockErrorService(500, 'Internal Server Error');\n * ```\n */\nexport function createMockErrorService(statusCode: number = 500, errorMessage: string = \"Error\"): MockService {\n const errorResponse = createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n });\n\n return {\n get: vi.fn().mockResolvedValue(errorResponse),\n post: vi.fn().mockResolvedValue(errorResponse),\n put: vi.fn().mockResolvedValue(errorResponse),\n patch: vi.fn().mockResolvedValue(errorResponse),\n delete: vi.fn().mockResolvedValue(errorResponse),\n };\n}\n","import { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockApiDataOptions {\n type: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n included?: any[];\n createdAt?: Date;\n updatedAt?: Date;\n self?: string;\n}\n\n/**\n * Creates a mock ApiDataInterface object for testing.\n *\n * @example\n * ```ts\n * import { createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test Article', body: 'Content here' },\n * });\n * ```\n *\n * @example With relationships\n * ```ts\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test' },\n * relationships: {\n * author: { data: { type: 'users', id: '42' } },\n * },\n * });\n * ```\n */\nexport function createMockApiData(options: CreateMockApiDataOptions): ApiDataInterface {\n const {\n type,\n id = `mock-${type}-${Math.random().toString(36).substring(7)}`,\n attributes = {},\n relationships = {},\n included = [],\n createdAt = new Date(),\n updatedAt = new Date(),\n self,\n } = options;\n\n const jsonApiData = {\n type,\n id,\n attributes: {\n ...attributes,\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n },\n relationships,\n };\n\n const mockData: ApiDataInterface = {\n get included() {\n return included;\n },\n get type() {\n return type;\n },\n get id() {\n return id;\n },\n get createdAt() {\n return createdAt;\n },\n get updatedAt() {\n return updatedAt;\n },\n get self() {\n return self;\n },\n get jsonApi() {\n return jsonApiData;\n },\n generateApiUrl: (params?: any) => {\n const baseUrl = `/${type}/${id}`;\n if (params) {\n const searchParams = new URLSearchParams(params);\n return `${baseUrl}?${searchParams.toString()}`;\n }\n return baseUrl;\n },\n dehydrate: () => ({\n jsonApi: jsonApiData,\n included,\n allData: [jsonApiData],\n }),\n rehydrate: function (data: any) {\n return this;\n },\n createJsonApi: (data: any) => ({\n type,\n id,\n attributes: data,\n }),\n };\n\n // Add attribute accessors to the mock object\n Object.keys(attributes).forEach((key) => {\n Object.defineProperty(mockData, key, {\n get: () => attributes[key],\n enumerable: true,\n });\n });\n\n return mockData;\n}\n\n/**\n * Creates an array of mock ApiDataInterface objects.\n *\n * @example\n * ```ts\n * import { createMockApiDataList } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticles = createMockApiDataList('articles', 5, (index) => ({\n * title: `Article ${index + 1}`,\n * }));\n * ```\n */\nexport function createMockApiDataList(\n type: string,\n count: number,\n attributesFactory?: (index: number) => Record<string, any>,\n): ApiDataInterface[] {\n return Array.from({ length: count }, (_, index) =>\n createMockApiData({\n type,\n id: `${index + 1}`,\n attributes: attributesFactory?.(index) ?? {},\n }),\n );\n}\n","import { expect } from \"vitest\";\n\ninterface JsonApiResponse {\n data?:\n | {\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }\n | Array<{\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }>;\n}\n\n/**\n * Custom Vitest matchers for JSON:API assertions.\n *\n * @example\n * ```ts\n * import { jsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n * import { expect } from 'vitest';\n *\n * expect.extend(jsonApiMatchers);\n *\n * // Then use in tests:\n * expect(response).toBeValidJsonApi();\n * expect(response).toHaveJsonApiType('articles');\n * expect(response).toHaveJsonApiAttribute('title', 'My Article');\n * expect(response).toHaveJsonApiRelationship('author');\n * ```\n */\nexport const jsonApiMatchers = {\n /**\n * Asserts that the response has a valid JSON:API structure with type and id.\n */\n toBeValidJsonApi(received: JsonApiResponse) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const hasType = typeof data?.type === \"string\" && data.type.length > 0;\n const hasId = typeof data?.id === \"string\" && data.id.length > 0;\n const isValid = hasType && hasId;\n\n return {\n pass: isValid,\n message: () =>\n isValid\n ? `Expected response not to be valid JSON:API, but it has type \"${data?.type}\" and id \"${data?.id}\"`\n : `Expected response to be valid JSON:API with type and id, but got type: ${JSON.stringify(data?.type)}, id: ${JSON.stringify(data?.id)}`,\n };\n },\n\n /**\n * Asserts that the response data has the expected JSON:API type.\n */\n toHaveJsonApiType(received: JsonApiResponse, expectedType: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const actualType = data?.type;\n const pass = actualType === expectedType;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have JSON:API type \"${expectedType}\"`\n : `Expected response to have JSON:API type \"${expectedType}\", but got \"${actualType}\"`,\n };\n },\n\n /**\n * Asserts that the response data has an attribute with the expected value.\n */\n toHaveJsonApiAttribute(received: JsonApiResponse, attributeName: string, expectedValue?: any) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const attributes = data?.attributes ?? {};\n const hasAttribute = attributeName in attributes;\n const actualValue = attributes[attributeName];\n\n if (expectedValue === undefined) {\n // Just check existence\n return {\n pass: hasAttribute,\n message: () =>\n hasAttribute\n ? `Expected response not to have JSON:API attribute \"${attributeName}\"`\n : `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found. Available attributes: ${Object.keys(attributes).join(\", \") || \"none\"}`,\n };\n }\n\n const valuesMatch = actualValue === expectedValue;\n return {\n pass: hasAttribute && valuesMatch,\n message: () =>\n hasAttribute && valuesMatch\n ? `Expected response not to have JSON:API attribute \"${attributeName}\" with value \"${expectedValue}\"`\n : !hasAttribute\n ? `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found`\n : `Expected JSON:API attribute \"${attributeName}\" to be \"${expectedValue}\", but got \"${actualValue}\"`,\n };\n },\n\n /**\n * Asserts that the response data has the specified relationship.\n */\n toHaveJsonApiRelationship(received: JsonApiResponse, relationshipName: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const relationships = data?.relationships ?? {};\n const hasRelationship = relationshipName in relationships;\n\n return {\n pass: hasRelationship,\n message: () =>\n hasRelationship\n ? `Expected response not to have JSON:API relationship \"${relationshipName}\"`\n : `Expected response to have JSON:API relationship \"${relationshipName}\", but it was not found. Available relationships: ${Object.keys(relationships).join(\", \") || \"none\"}`,\n };\n },\n\n /**\n * Asserts that the response data array has the expected length.\n */\n toHaveJsonApiLength(received: JsonApiResponse, expectedLength: number) {\n const data = received?.data;\n const isArray = Array.isArray(data);\n const actualLength = isArray ? data.length : data ? 1 : 0;\n const pass = actualLength === expectedLength;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have ${expectedLength} items`\n : `Expected response to have ${expectedLength} items, but got ${actualLength}`,\n };\n },\n};\n\n// Type declarations for the custom matchers\ndeclare module \"vitest\" {\n interface Assertion<T = any> {\n toBeValidJsonApi(): T;\n toHaveJsonApiType(expectedType: string): T;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): T;\n toHaveJsonApiRelationship(relationshipName: string): T;\n toHaveJsonApiLength(expectedLength: number): T;\n }\n interface AsymmetricMatchersContaining {\n toBeValidJsonApi(): any;\n toHaveJsonApiType(expectedType: string): any;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;\n toHaveJsonApiRelationship(relationshipName: string): any;\n toHaveJsonApiLength(expectedLength: number): any;\n }\n}\n\n/**\n * Extends Vitest's expect with JSON:API matchers.\n * Call this in your test setup file.\n *\n * @example\n * ```ts\n * // vitest.setup.ts\n * import { extendExpectWithJsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * extendExpectWithJsonApiMatchers();\n * ```\n */\nexport function extendExpectWithJsonApiMatchers(): void {\n expect.extend(jsonApiMatchers);\n}\n","\"use client\";\n\nimport React, { ReactElement } from \"react\";\nimport { render, RenderOptions, RenderResult } from \"@testing-library/react\";\nimport { MockJsonApiProvider, MockJsonApiProviderProps } from \"../providers/MockJsonApiProvider\";\nimport { JsonApiConfig } from \"../../client/context/JsonApiContext\";\n\nexport interface RenderWithProvidersOptions extends Omit<RenderOptions, \"wrapper\"> {\n /**\n * Custom JSON:API configuration to pass to the mock provider.\n */\n jsonApiConfig?: Partial<JsonApiConfig>;\n\n /**\n * Additional wrapper component to wrap around the providers.\n */\n wrapper?: React.ComponentType<{ children: React.ReactNode }>;\n}\n\n/**\n * Renders a component wrapped with all necessary providers for testing.\n *\n * @example\n * ```tsx\n * import { renderWithProviders } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const { getByText } = renderWithProviders(<MyComponent />);\n * expect(getByText('Hello')).toBeInTheDocument();\n * ```\n *\n * @example With custom config\n * ```tsx\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * jsonApiConfig: { apiUrl: 'https://custom.api.com' },\n * });\n * ```\n *\n * @example With additional wrapper\n * ```tsx\n * const CustomWrapper = ({ children }) => (\n * <ThemeProvider>{children}</ThemeProvider>\n * );\n *\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * wrapper: CustomWrapper,\n * });\n * ```\n */\nexport function renderWithProviders(\n ui: ReactElement,\n options: RenderWithProvidersOptions = {}\n): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = (\n <MockJsonApiProvider config={jsonApiConfig}>\n {children}\n </MockJsonApiProvider>\n );\n\n if (AdditionalWrapper) {\n return <AdditionalWrapper>{content}</AdditionalWrapper>;\n }\n\n return content;\n }\n\n return render(ui, { wrapper: AllProviders, ...renderOptions });\n}\n\n/**\n * Re-export render utilities from Testing Library for convenience.\n */\nexport { render, screen, waitFor, fireEvent, within } from \"@testing-library/react\";\nexport { userEvent } from \"@testing-library/user-event\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/providers/MockJsonApiProvider.tsx","../../src/testing/factories/createMockModule.ts","../../src/testing/factories/createMockResponse.ts","../../src/testing/factories/createMockService.ts","../../src/testing/factories/createMockApiData.ts","../../src/testing/matchers/jsonApiMatchers.ts","../../src/testing/utils/renderWithProviders.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport { JsonApiConfig, JsonApiContext } from \"../../client/context/JsonApiContext\";\n\nexport interface MockJsonApiProviderProps {\n children: React.ReactNode;\n config?: Partial<JsonApiConfig>;\n}\n\nconst defaultMockConfig: JsonApiConfig = {\n apiUrl: \"https://api.test.com\",\n tokenGetter: async () => \"mock-token-for-testing\",\n languageGetter: async () => \"en\",\n defaultHeaders: {},\n onError: () => {},\n cacheConfig: {\n defaultProfile: \"default\",\n },\n};\n\n/**\n * A test-friendly provider that wraps components with mock JSON:API context.\n *\n * @example\n * ```tsx\n * import { MockJsonApiProvider } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * render(\n * <MockJsonApiProvider>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n *\n * @example With custom config\n * ```tsx\n * render(\n * <MockJsonApiProvider config={{ apiUrl: 'https://custom.api.com' }}>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n */\nexport function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps) {\n const mergedConfig: JsonApiConfig = {\n ...defaultMockConfig,\n ...config,\n };\n\n return (\n <JsonApiContext.Provider value={mergedConfig}>\n {children}\n </JsonApiContext.Provider>\n );\n}\n\nexport { defaultMockConfig };\n","import { ApiRequestDataTypeInterface } from \"../../core/interfaces/ApiRequestDataTypeInterface\";\n\nexport interface CreateMockModuleOptions {\n name: string;\n cache?: string;\n inclusions?: ApiRequestDataTypeInterface[\"inclusions\"];\n}\n\n/**\n * Creates a mock module definition for testing.\n *\n * @example\n * ```ts\n * import { createMockModule } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticleModule = createMockModule({ name: 'articles' });\n * ```\n */\nexport function createMockModule(options: CreateMockModuleOptions): ApiRequestDataTypeInterface {\n // Create a mock model class\n class MockModel {\n id = \"mock-id\";\n type = options.name;\n }\n\n return {\n name: options.name,\n cache: options.cache,\n inclusions: options.inclusions,\n model: MockModel,\n };\n}\n","import { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockResponseOptions {\n data?: ApiDataInterface | ApiDataInterface[] | null;\n ok?: boolean;\n response?: number;\n error?: string;\n meta?: Record<string, any>;\n self?: string;\n next?: string;\n prev?: string;\n}\n\n/**\n * Creates a mock API response for testing.\n *\n * @example\n * ```ts\n * import { createMockResponse, createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockData = createMockApiData({ type: 'articles', id: '1' });\n * const response = createMockResponse({ data: mockData, ok: true });\n * ```\n *\n * @example With pagination\n * ```ts\n * const response = createMockResponse({\n * data: [mockData],\n * ok: true,\n * next: '/articles?page=2',\n * prev: '/articles?page=0',\n * });\n * ```\n */\nexport function createMockResponse(options: CreateMockResponseOptions = {}): ApiResponseInterface {\n const {\n data = null,\n ok = true,\n response = ok ? 200 : 500,\n error = ok ? \"\" : \"Error\",\n meta,\n self,\n next,\n prev,\n } = options;\n\n const mockResponse: ApiResponseInterface = {\n ok,\n response,\n data: data as ApiDataInterface | ApiDataInterface[],\n error,\n meta,\n self,\n next,\n prev,\n };\n\n // Add pagination methods if next/prev provided\n if (next) {\n mockResponse.nextPage = async () =>\n createMockResponse({ ...options, next: undefined, prev: self });\n }\n\n if (prev) {\n mockResponse.prevPage = async () =>\n createMockResponse({ ...options, prev: undefined, next: self });\n }\n\n return mockResponse;\n}\n\n/**\n * Creates a mock error response for testing error scenarios.\n *\n * @example\n * ```ts\n * import { createMockErrorResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorResponse = createMockErrorResponse(404, 'Not Found');\n * ```\n */\nexport function createMockErrorResponse(\n statusCode: number,\n errorMessage: string\n): ApiResponseInterface {\n return createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n data: null,\n });\n}\n","import { vi, type Mock } from \"vitest\";\nimport { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { createMockResponse } from \"./createMockResponse\";\n\nexport type MockApiMethod = Mock<(...args: any[]) => Promise<ApiResponseInterface>>;\n\nexport interface MockService {\n get: MockApiMethod;\n post: MockApiMethod;\n put: MockApiMethod;\n patch: MockApiMethod;\n delete: MockApiMethod;\n}\n\nexport interface CreateMockServiceOptions {\n defaultResponse?: ApiResponseInterface;\n}\n\n/**\n * Creates a mock service with Vitest mock functions for all HTTP methods.\n *\n * @example\n * ```ts\n * import { createMockService, createMockResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockService = createMockService();\n * mockService.get.mockResolvedValue(createMockResponse({ data: mockData }));\n *\n * // Use in test\n * expect(mockService.get).toHaveBeenCalled();\n * ```\n *\n * @example With default response\n * ```ts\n * const mockService = createMockService({\n * defaultResponse: createMockResponse({ ok: true, data: [] }),\n * });\n * ```\n */\nexport function createMockService(options: CreateMockServiceOptions = {}): MockService {\n const defaultResponse = options.defaultResponse ?? createMockResponse({ ok: true });\n\n return {\n get: vi.fn().mockResolvedValue(defaultResponse),\n post: vi.fn().mockResolvedValue(defaultResponse),\n put: vi.fn().mockResolvedValue(defaultResponse),\n patch: vi.fn().mockResolvedValue(defaultResponse),\n delete: vi.fn().mockResolvedValue(defaultResponse),\n };\n}\n\n/**\n * Creates a mock service that returns errors for all methods.\n *\n * @example\n * ```ts\n * import { createMockErrorService } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorService = createMockErrorService(500, 'Internal Server Error');\n * ```\n */\nexport function createMockErrorService(\n statusCode: number = 500,\n errorMessage: string = \"Error\"\n): MockService {\n const errorResponse = createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n });\n\n return {\n get: vi.fn().mockResolvedValue(errorResponse),\n post: vi.fn().mockResolvedValue(errorResponse),\n put: vi.fn().mockResolvedValue(errorResponse),\n patch: vi.fn().mockResolvedValue(errorResponse),\n delete: vi.fn().mockResolvedValue(errorResponse),\n };\n}\n","import { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockApiDataOptions {\n type: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n included?: any[];\n createdAt?: Date;\n updatedAt?: Date;\n self?: string;\n}\n\n/**\n * Creates a mock ApiDataInterface object for testing.\n *\n * @example\n * ```ts\n * import { createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test Article', body: 'Content here' },\n * });\n * ```\n *\n * @example With relationships\n * ```ts\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test' },\n * relationships: {\n * author: { data: { type: 'users', id: '42' } },\n * },\n * });\n * ```\n */\nexport function createMockApiData(options: CreateMockApiDataOptions): ApiDataInterface {\n const {\n type,\n id = `mock-${type}-${Math.random().toString(36).substring(7)}`,\n attributes = {},\n relationships = {},\n included = [],\n createdAt = new Date(),\n updatedAt = new Date(),\n self,\n } = options;\n\n const jsonApiData = {\n type,\n id,\n attributes: {\n ...attributes,\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n },\n relationships,\n };\n\n const mockData: ApiDataInterface = {\n get included() {\n return included;\n },\n get type() {\n return type;\n },\n get id() {\n return id;\n },\n get createdAt() {\n return createdAt;\n },\n get updatedAt() {\n return updatedAt;\n },\n get self() {\n return self;\n },\n get jsonApi() {\n return jsonApiData;\n },\n generateApiUrl: (params?: any) => {\n const baseUrl = `/${type}/${id}`;\n if (params) {\n const searchParams = new URLSearchParams(params);\n return `${baseUrl}?${searchParams.toString()}`;\n }\n return baseUrl;\n },\n dehydrate: () => ({\n jsonApi: jsonApiData,\n included,\n allData: [jsonApiData],\n }),\n rehydrate: function (data: any) {\n return this;\n },\n createJsonApi: (data: any) => ({\n type,\n id,\n attributes: data,\n }),\n };\n\n // Add attribute accessors to the mock object\n Object.keys(attributes).forEach((key) => {\n Object.defineProperty(mockData, key, {\n get: () => attributes[key],\n enumerable: true,\n });\n });\n\n return mockData;\n}\n\n/**\n * Creates an array of mock ApiDataInterface objects.\n *\n * @example\n * ```ts\n * import { createMockApiDataList } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticles = createMockApiDataList('articles', 5, (index) => ({\n * title: `Article ${index + 1}`,\n * }));\n * ```\n */\nexport function createMockApiDataList(\n type: string,\n count: number,\n attributesFactory?: (index: number) => Record<string, any>\n): ApiDataInterface[] {\n return Array.from({ length: count }, (_, index) =>\n createMockApiData({\n type,\n id: `${index + 1}`,\n attributes: attributesFactory?.(index) ?? {},\n })\n );\n}\n","import { expect } from \"vitest\";\n\ninterface JsonApiResponse {\n data?: {\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n } | Array<{\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }>;\n}\n\n/**\n * Custom Vitest matchers for JSON:API assertions.\n *\n * @example\n * ```ts\n * import { jsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n * import { expect } from 'vitest';\n *\n * expect.extend(jsonApiMatchers);\n *\n * // Then use in tests:\n * expect(response).toBeValidJsonApi();\n * expect(response).toHaveJsonApiType('articles');\n * expect(response).toHaveJsonApiAttribute('title', 'My Article');\n * expect(response).toHaveJsonApiRelationship('author');\n * ```\n */\nexport const jsonApiMatchers = {\n /**\n * Asserts that the response has a valid JSON:API structure with type and id.\n */\n toBeValidJsonApi(received: JsonApiResponse) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const hasType = typeof data?.type === \"string\" && data.type.length > 0;\n const hasId = typeof data?.id === \"string\" && data.id.length > 0;\n const isValid = hasType && hasId;\n\n return {\n pass: isValid,\n message: () =>\n isValid\n ? `Expected response not to be valid JSON:API, but it has type \"${data?.type}\" and id \"${data?.id}\"`\n : `Expected response to be valid JSON:API with type and id, but got type: ${JSON.stringify(data?.type)}, id: ${JSON.stringify(data?.id)}`,\n };\n },\n\n /**\n * Asserts that the response data has the expected JSON:API type.\n */\n toHaveJsonApiType(received: JsonApiResponse, expectedType: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const actualType = data?.type;\n const pass = actualType === expectedType;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have JSON:API type \"${expectedType}\"`\n : `Expected response to have JSON:API type \"${expectedType}\", but got \"${actualType}\"`,\n };\n },\n\n /**\n * Asserts that the response data has an attribute with the expected value.\n */\n toHaveJsonApiAttribute(\n received: JsonApiResponse,\n attributeName: string,\n expectedValue?: any\n ) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const attributes = data?.attributes ?? {};\n const hasAttribute = attributeName in attributes;\n const actualValue = attributes[attributeName];\n\n if (expectedValue === undefined) {\n // Just check existence\n return {\n pass: hasAttribute,\n message: () =>\n hasAttribute\n ? `Expected response not to have JSON:API attribute \"${attributeName}\"`\n : `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found. Available attributes: ${Object.keys(attributes).join(\", \") || \"none\"}`,\n };\n }\n\n const valuesMatch = actualValue === expectedValue;\n return {\n pass: hasAttribute && valuesMatch,\n message: () =>\n hasAttribute && valuesMatch\n ? `Expected response not to have JSON:API attribute \"${attributeName}\" with value \"${expectedValue}\"`\n : !hasAttribute\n ? `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found`\n : `Expected JSON:API attribute \"${attributeName}\" to be \"${expectedValue}\", but got \"${actualValue}\"`,\n };\n },\n\n /**\n * Asserts that the response data has the specified relationship.\n */\n toHaveJsonApiRelationship(received: JsonApiResponse, relationshipName: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const relationships = data?.relationships ?? {};\n const hasRelationship = relationshipName in relationships;\n\n return {\n pass: hasRelationship,\n message: () =>\n hasRelationship\n ? `Expected response not to have JSON:API relationship \"${relationshipName}\"`\n : `Expected response to have JSON:API relationship \"${relationshipName}\", but it was not found. Available relationships: ${Object.keys(relationships).join(\", \") || \"none\"}`,\n };\n },\n\n /**\n * Asserts that the response data array has the expected length.\n */\n toHaveJsonApiLength(received: JsonApiResponse, expectedLength: number) {\n const data = received?.data;\n const isArray = Array.isArray(data);\n const actualLength = isArray ? data.length : data ? 1 : 0;\n const pass = actualLength === expectedLength;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have ${expectedLength} items`\n : `Expected response to have ${expectedLength} items, but got ${actualLength}`,\n };\n },\n};\n\n// Type declarations for the custom matchers\ndeclare module \"vitest\" {\n interface Assertion<T = any> {\n toBeValidJsonApi(): T;\n toHaveJsonApiType(expectedType: string): T;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): T;\n toHaveJsonApiRelationship(relationshipName: string): T;\n toHaveJsonApiLength(expectedLength: number): T;\n }\n interface AsymmetricMatchersContaining {\n toBeValidJsonApi(): any;\n toHaveJsonApiType(expectedType: string): any;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;\n toHaveJsonApiRelationship(relationshipName: string): any;\n toHaveJsonApiLength(expectedLength: number): any;\n }\n}\n\n/**\n * Extends Vitest's expect with JSON:API matchers.\n * Call this in your test setup file.\n *\n * @example\n * ```ts\n * // vitest.setup.ts\n * import { extendExpectWithJsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * extendExpectWithJsonApiMatchers();\n * ```\n */\nexport function extendExpectWithJsonApiMatchers(): void {\n expect.extend(jsonApiMatchers);\n}\n","\"use client\";\n\nimport React, { ReactElement } from \"react\";\nimport { render, RenderOptions, RenderResult } from \"@testing-library/react\";\nimport { MockJsonApiProvider, MockJsonApiProviderProps } from \"../providers/MockJsonApiProvider\";\nimport { JsonApiConfig } from \"../../client/context/JsonApiContext\";\n\nexport interface RenderWithProvidersOptions extends Omit<RenderOptions, \"wrapper\"> {\n /**\n * Custom JSON:API configuration to pass to the mock provider.\n */\n jsonApiConfig?: Partial<JsonApiConfig>;\n\n /**\n * Additional wrapper component to wrap around the providers.\n */\n wrapper?: React.ComponentType<{ children: React.ReactNode }>;\n}\n\n/**\n * Renders a component wrapped with all necessary providers for testing.\n *\n * @example\n * ```tsx\n * import { renderWithProviders } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const { getByText } = renderWithProviders(<MyComponent />);\n * expect(getByText('Hello')).toBeInTheDocument();\n * ```\n *\n * @example With custom config\n * ```tsx\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * jsonApiConfig: { apiUrl: 'https://custom.api.com' },\n * });\n * ```\n *\n * @example With additional wrapper\n * ```tsx\n * const CustomWrapper = ({ children }) => (\n * <ThemeProvider>{children}</ThemeProvider>\n * );\n *\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * wrapper: CustomWrapper,\n * });\n * ```\n */\nexport function renderWithProviders(\n ui: ReactElement,\n options: RenderWithProvidersOptions = {}\n): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = (\n <MockJsonApiProvider config={jsonApiConfig}>\n {children}\n </MockJsonApiProvider>\n );\n\n if (AdditionalWrapper) {\n return <AdditionalWrapper>{content}</AdditionalWrapper>;\n }\n\n return content;\n }\n\n return render(ui, { wrapper: AllProviders, ...renderOptions });\n}\n\n/**\n * Re-export render utilities from Testing Library for convenience.\n */\nexport { render, screen, waitFor, fireEvent, within } from \"@testing-library/react\";\nexport { userEvent } from \"@testing-library/user-event\";\n"],"mappings":"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","names":["render","jsx"]}
1
+ {"version":3,"sources":["../../src/testing/providers/MockJsonApiProvider.tsx","../../src/testing/factories/createMockModule.ts","../../src/testing/factories/createMockResponse.ts","../../src/testing/factories/createMockService.ts","../../src/testing/factories/createMockApiData.ts","../../src/testing/matchers/jsonApiMatchers.ts","../../src/testing/utils/renderWithProviders.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport { JsonApiConfig, JsonApiContext } from \"../../client/context/JsonApiContext\";\n\nexport interface MockJsonApiProviderProps {\n children: React.ReactNode;\n config?: Partial<JsonApiConfig>;\n}\n\nconst defaultMockConfig: JsonApiConfig = {\n apiUrl: \"https://api.test.com\",\n tokenGetter: async () => \"mock-token-for-testing\",\n languageGetter: async () => \"en\",\n defaultHeaders: {},\n onError: () => {},\n cacheConfig: {\n defaultProfile: \"default\",\n },\n};\n\n/**\n * A test-friendly provider that wraps components with mock JSON:API context.\n *\n * @example\n * ```tsx\n * import { MockJsonApiProvider } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * render(\n * <MockJsonApiProvider>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n *\n * @example With custom config\n * ```tsx\n * render(\n * <MockJsonApiProvider config={{ apiUrl: 'https://custom.api.com' }}>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n */\nexport function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps) {\n const mergedConfig: JsonApiConfig = {\n ...defaultMockConfig,\n ...config,\n };\n\n return (\n <JsonApiContext.Provider value={mergedConfig}>\n {children}\n </JsonApiContext.Provider>\n );\n}\n\nexport { defaultMockConfig };\n","import { ApiRequestDataTypeInterface } from \"../../core/interfaces/ApiRequestDataTypeInterface\";\n\nexport interface CreateMockModuleOptions {\n name: string;\n cache?: string;\n inclusions?: ApiRequestDataTypeInterface[\"inclusions\"];\n}\n\n/**\n * Creates a mock module definition for testing.\n *\n * @example\n * ```ts\n * import { createMockModule } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticleModule = createMockModule({ name: 'articles' });\n * ```\n */\nexport function createMockModule(options: CreateMockModuleOptions): ApiRequestDataTypeInterface {\n // Create a mock model class\n class MockModel {\n id = \"mock-id\";\n type = options.name;\n }\n\n return {\n name: options.name,\n cache: options.cache,\n inclusions: options.inclusions,\n model: MockModel,\n };\n}\n","import { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockResponseOptions {\n data?: ApiDataInterface | ApiDataInterface[] | null;\n ok?: boolean;\n response?: number;\n error?: string;\n meta?: Record<string, any>;\n self?: string;\n next?: string;\n prev?: string;\n}\n\n/**\n * Creates a mock API response for testing.\n *\n * @example\n * ```ts\n * import { createMockResponse, createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockData = createMockApiData({ type: 'articles', id: '1' });\n * const response = createMockResponse({ data: mockData, ok: true });\n * ```\n *\n * @example With pagination\n * ```ts\n * const response = createMockResponse({\n * data: [mockData],\n * ok: true,\n * next: '/articles?page=2',\n * prev: '/articles?page=0',\n * });\n * ```\n */\nexport function createMockResponse(options: CreateMockResponseOptions = {}): ApiResponseInterface {\n const {\n data = null,\n ok = true,\n response = ok ? 200 : 500,\n error = ok ? \"\" : \"Error\",\n meta,\n self,\n next,\n prev,\n } = options;\n\n const mockResponse: ApiResponseInterface = {\n ok,\n response,\n data: data as ApiDataInterface | ApiDataInterface[],\n error,\n meta,\n self,\n next,\n prev,\n };\n\n // Add pagination methods if next/prev provided\n if (next) {\n mockResponse.nextPage = async () => createMockResponse({ ...options, next: undefined, prev: self });\n }\n\n if (prev) {\n mockResponse.prevPage = async () => createMockResponse({ ...options, prev: undefined, next: self });\n }\n\n return mockResponse;\n}\n\n/**\n * Creates a mock error response for testing error scenarios.\n *\n * @example\n * ```ts\n * import { createMockErrorResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorResponse = createMockErrorResponse(404, 'Not Found');\n * ```\n */\nexport function createMockErrorResponse(statusCode: number, errorMessage: string): ApiResponseInterface {\n return createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n data: null,\n });\n}\n","import { vi, type Mock } from \"vitest\";\nimport { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { createMockResponse } from \"./createMockResponse\";\n\nexport type MockApiMethod = Mock<(...args: any[]) => Promise<ApiResponseInterface>>;\n\nexport interface MockService {\n get: MockApiMethod;\n post: MockApiMethod;\n put: MockApiMethod;\n patch: MockApiMethod;\n delete: MockApiMethod;\n}\n\nexport interface CreateMockServiceOptions {\n defaultResponse?: ApiResponseInterface;\n}\n\n/**\n * Creates a mock service with Vitest mock functions for all HTTP methods.\n *\n * @example\n * ```ts\n * import { createMockService, createMockResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockService = createMockService();\n * mockService.get.mockResolvedValue(createMockResponse({ data: mockData }));\n *\n * // Use in test\n * expect(mockService.get).toHaveBeenCalled();\n * ```\n *\n * @example With default response\n * ```ts\n * const mockService = createMockService({\n * defaultResponse: createMockResponse({ ok: true, data: [] }),\n * });\n * ```\n */\nexport function createMockService(options: CreateMockServiceOptions = {}): MockService {\n const defaultResponse = options.defaultResponse ?? createMockResponse({ ok: true });\n\n return {\n get: vi.fn().mockResolvedValue(defaultResponse),\n post: vi.fn().mockResolvedValue(defaultResponse),\n put: vi.fn().mockResolvedValue(defaultResponse),\n patch: vi.fn().mockResolvedValue(defaultResponse),\n delete: vi.fn().mockResolvedValue(defaultResponse),\n };\n}\n\n/**\n * Creates a mock service that returns errors for all methods.\n *\n * @example\n * ```ts\n * import { createMockErrorService } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorService = createMockErrorService(500, 'Internal Server Error');\n * ```\n */\nexport function createMockErrorService(statusCode: number = 500, errorMessage: string = \"Error\"): MockService {\n const errorResponse = createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n });\n\n return {\n get: vi.fn().mockResolvedValue(errorResponse),\n post: vi.fn().mockResolvedValue(errorResponse),\n put: vi.fn().mockResolvedValue(errorResponse),\n patch: vi.fn().mockResolvedValue(errorResponse),\n delete: vi.fn().mockResolvedValue(errorResponse),\n };\n}\n","import { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockApiDataOptions {\n type: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n included?: any[];\n createdAt?: Date;\n updatedAt?: Date;\n self?: string;\n}\n\n/**\n * Creates a mock ApiDataInterface object for testing.\n *\n * @example\n * ```ts\n * import { createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test Article', body: 'Content here' },\n * });\n * ```\n *\n * @example With relationships\n * ```ts\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test' },\n * relationships: {\n * author: { data: { type: 'users', id: '42' } },\n * },\n * });\n * ```\n */\nexport function createMockApiData(options: CreateMockApiDataOptions): ApiDataInterface {\n const {\n type,\n id = `mock-${type}-${Math.random().toString(36).substring(7)}`,\n attributes = {},\n relationships = {},\n included = [],\n createdAt = new Date(),\n updatedAt = new Date(),\n self,\n } = options;\n\n const jsonApiData = {\n type,\n id,\n attributes: {\n ...attributes,\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n },\n relationships,\n };\n\n const mockData: ApiDataInterface = {\n get included() {\n return included;\n },\n get type() {\n return type;\n },\n get id() {\n return id;\n },\n get createdAt() {\n return createdAt;\n },\n get updatedAt() {\n return updatedAt;\n },\n get self() {\n return self;\n },\n get jsonApi() {\n return jsonApiData;\n },\n generateApiUrl: (params?: any) => {\n const baseUrl = `/${type}/${id}`;\n if (params) {\n const searchParams = new URLSearchParams(params);\n return `${baseUrl}?${searchParams.toString()}`;\n }\n return baseUrl;\n },\n dehydrate: () => ({\n jsonApi: jsonApiData,\n included,\n allData: [jsonApiData],\n }),\n rehydrate: function (data: any) {\n return this;\n },\n createJsonApi: (data: any) => ({\n type,\n id,\n attributes: data,\n }),\n };\n\n // Add attribute accessors to the mock object\n Object.keys(attributes).forEach((key) => {\n Object.defineProperty(mockData, key, {\n get: () => attributes[key],\n enumerable: true,\n });\n });\n\n return mockData;\n}\n\n/**\n * Creates an array of mock ApiDataInterface objects.\n *\n * @example\n * ```ts\n * import { createMockApiDataList } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticles = createMockApiDataList('articles', 5, (index) => ({\n * title: `Article ${index + 1}`,\n * }));\n * ```\n */\nexport function createMockApiDataList(\n type: string,\n count: number,\n attributesFactory?: (index: number) => Record<string, any>,\n): ApiDataInterface[] {\n return Array.from({ length: count }, (_, index) =>\n createMockApiData({\n type,\n id: `${index + 1}`,\n attributes: attributesFactory?.(index) ?? {},\n }),\n );\n}\n","import { expect } from \"vitest\";\n\ninterface JsonApiResponse {\n data?:\n | {\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }\n | Array<{\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }>;\n}\n\n/**\n * Custom Vitest matchers for JSON:API assertions.\n *\n * @example\n * ```ts\n * import { jsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n * import { expect } from 'vitest';\n *\n * expect.extend(jsonApiMatchers);\n *\n * // Then use in tests:\n * expect(response).toBeValidJsonApi();\n * expect(response).toHaveJsonApiType('articles');\n * expect(response).toHaveJsonApiAttribute('title', 'My Article');\n * expect(response).toHaveJsonApiRelationship('author');\n * ```\n */\nexport const jsonApiMatchers = {\n /**\n * Asserts that the response has a valid JSON:API structure with type and id.\n */\n toBeValidJsonApi(received: JsonApiResponse) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const hasType = typeof data?.type === \"string\" && data.type.length > 0;\n const hasId = typeof data?.id === \"string\" && data.id.length > 0;\n const isValid = hasType && hasId;\n\n return {\n pass: isValid,\n message: () =>\n isValid\n ? `Expected response not to be valid JSON:API, but it has type \"${data?.type}\" and id \"${data?.id}\"`\n : `Expected response to be valid JSON:API with type and id, but got type: ${JSON.stringify(data?.type)}, id: ${JSON.stringify(data?.id)}`,\n };\n },\n\n /**\n * Asserts that the response data has the expected JSON:API type.\n */\n toHaveJsonApiType(received: JsonApiResponse, expectedType: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const actualType = data?.type;\n const pass = actualType === expectedType;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have JSON:API type \"${expectedType}\"`\n : `Expected response to have JSON:API type \"${expectedType}\", but got \"${actualType}\"`,\n };\n },\n\n /**\n * Asserts that the response data has an attribute with the expected value.\n */\n toHaveJsonApiAttribute(received: JsonApiResponse, attributeName: string, expectedValue?: any) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const attributes = data?.attributes ?? {};\n const hasAttribute = attributeName in attributes;\n const actualValue = attributes[attributeName];\n\n if (expectedValue === undefined) {\n // Just check existence\n return {\n pass: hasAttribute,\n message: () =>\n hasAttribute\n ? `Expected response not to have JSON:API attribute \"${attributeName}\"`\n : `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found. Available attributes: ${Object.keys(attributes).join(\", \") || \"none\"}`,\n };\n }\n\n const valuesMatch = actualValue === expectedValue;\n return {\n pass: hasAttribute && valuesMatch,\n message: () =>\n hasAttribute && valuesMatch\n ? `Expected response not to have JSON:API attribute \"${attributeName}\" with value \"${expectedValue}\"`\n : !hasAttribute\n ? `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found`\n : `Expected JSON:API attribute \"${attributeName}\" to be \"${expectedValue}\", but got \"${actualValue}\"`,\n };\n },\n\n /**\n * Asserts that the response data has the specified relationship.\n */\n toHaveJsonApiRelationship(received: JsonApiResponse, relationshipName: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const relationships = data?.relationships ?? {};\n const hasRelationship = relationshipName in relationships;\n\n return {\n pass: hasRelationship,\n message: () =>\n hasRelationship\n ? `Expected response not to have JSON:API relationship \"${relationshipName}\"`\n : `Expected response to have JSON:API relationship \"${relationshipName}\", but it was not found. Available relationships: ${Object.keys(relationships).join(\", \") || \"none\"}`,\n };\n },\n\n /**\n * Asserts that the response data array has the expected length.\n */\n toHaveJsonApiLength(received: JsonApiResponse, expectedLength: number) {\n const data = received?.data;\n const isArray = Array.isArray(data);\n const actualLength = isArray ? data.length : data ? 1 : 0;\n const pass = actualLength === expectedLength;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have ${expectedLength} items`\n : `Expected response to have ${expectedLength} items, but got ${actualLength}`,\n };\n },\n};\n\n// Type declarations for the custom matchers\ndeclare module \"vitest\" {\n interface Assertion<T = any> {\n toBeValidJsonApi(): T;\n toHaveJsonApiType(expectedType: string): T;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): T;\n toHaveJsonApiRelationship(relationshipName: string): T;\n toHaveJsonApiLength(expectedLength: number): T;\n }\n interface AsymmetricMatchersContaining {\n toBeValidJsonApi(): any;\n toHaveJsonApiType(expectedType: string): any;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;\n toHaveJsonApiRelationship(relationshipName: string): any;\n toHaveJsonApiLength(expectedLength: number): any;\n }\n}\n\n/**\n * Extends Vitest's expect with JSON:API matchers.\n * Call this in your test setup file.\n *\n * @example\n * ```ts\n * // vitest.setup.ts\n * import { extendExpectWithJsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * extendExpectWithJsonApiMatchers();\n * ```\n */\nexport function extendExpectWithJsonApiMatchers(): void {\n expect.extend(jsonApiMatchers);\n}\n","\"use client\";\n\nimport React, { ReactElement } from \"react\";\nimport { render, RenderOptions, RenderResult } from \"@testing-library/react\";\nimport { MockJsonApiProvider, MockJsonApiProviderProps } from \"../providers/MockJsonApiProvider\";\nimport { JsonApiConfig } from \"../../client/context/JsonApiContext\";\n\nexport interface RenderWithProvidersOptions extends Omit<RenderOptions, \"wrapper\"> {\n /**\n * Custom JSON:API configuration to pass to the mock provider.\n */\n jsonApiConfig?: Partial<JsonApiConfig>;\n\n /**\n * Additional wrapper component to wrap around the providers.\n */\n wrapper?: React.ComponentType<{ children: React.ReactNode }>;\n}\n\n/**\n * Renders a component wrapped with all necessary providers for testing.\n *\n * @example\n * ```tsx\n * import { renderWithProviders } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const { getByText } = renderWithProviders(<MyComponent />);\n * expect(getByText('Hello')).toBeInTheDocument();\n * ```\n *\n * @example With custom config\n * ```tsx\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * jsonApiConfig: { apiUrl: 'https://custom.api.com' },\n * });\n * ```\n *\n * @example With additional wrapper\n * ```tsx\n * const CustomWrapper = ({ children }) => (\n * <ThemeProvider>{children}</ThemeProvider>\n * );\n *\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * wrapper: CustomWrapper,\n * });\n * ```\n */\nexport function renderWithProviders(\n ui: ReactElement,\n options: RenderWithProvidersOptions = {}\n): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = (\n <MockJsonApiProvider config={jsonApiConfig}>\n {children}\n </MockJsonApiProvider>\n );\n\n if (AdditionalWrapper) {\n return <AdditionalWrapper>{content}</AdditionalWrapper>;\n }\n\n return content;\n }\n\n return render(ui, { wrapper: AllProviders, ...renderOptions });\n}\n\n/**\n * Re-export render utilities from Testing Library for convenience.\n */\nexport { render, screen, waitFor, fireEvent, within } from \"@testing-library/react\";\nexport { userEvent } from \"@testing-library/user-event\";\n"],"mappings":"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","names":["render","jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carlonicora/nextjs-jsonapi",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Next.js JSON:API client with server/client support and caching",
5
5
  "author": "Carlo Nicora",
6
6
  "license": "GPL-3.0-or-later",
@@ -95,6 +95,8 @@
95
95
  }
96
96
  },
97
97
  "dependencies": {
98
+ "@base-ui/react": "^1.0.0",
99
+ "class-variance-authority": "^0.7.1",
98
100
  "@blocknote/core": "^0.44.2",
99
101
  "@blocknote/react": "^0.44.2",
100
102
  "@blocknote/shadcn": "^0.44.2",
@@ -102,31 +104,8 @@
102
104
  "@dnd-kit/sortable": "^10.0.0",
103
105
  "@dnd-kit/utilities": "^3.2.2",
104
106
  "@hookform/resolvers": "^5.2.2",
105
- "@radix-ui/react-accordion": "^1.2.12",
106
- "@radix-ui/react-alert-dialog": "^1.1.15",
107
- "@radix-ui/react-avatar": "^1.1.11",
108
- "@radix-ui/react-checkbox": "^1.3.3",
109
- "@radix-ui/react-collapsible": "^1.1.12",
110
- "@radix-ui/react-context-menu": "^2.2.16",
111
- "@radix-ui/react-dialog": "^1.1.15",
112
- "@radix-ui/react-dropdown-menu": "^2.1.16",
113
- "@radix-ui/react-hover-card": "^1.1.15",
114
- "@radix-ui/react-icons": "^1.3.2",
115
- "@radix-ui/react-label": "^2.1.8",
116
- "@radix-ui/react-navigation-menu": "^1.2.14",
117
- "@radix-ui/react-popover": "^1.1.15",
118
- "@radix-ui/react-progress": "^1.1.8",
119
- "@radix-ui/react-radio-group": "^1.3.8",
120
- "@radix-ui/react-scroll-area": "^1.2.10",
121
- "@radix-ui/react-select": "^2.2.6",
122
- "@radix-ui/react-separator": "^1.1.8",
123
- "@radix-ui/react-slider": "^1.3.6",
107
+ "@radix-ui/react-label": "^2.1.4",
124
108
  "@radix-ui/react-slot": "^1.2.4",
125
- "@radix-ui/react-switch": "^1.2.6",
126
- "@radix-ui/react-tabs": "^1.1.13",
127
- "@radix-ui/react-toggle": "^1.1.10",
128
- "@radix-ui/react-tooltip": "^1.2.8",
129
- "class-variance-authority": "^0.7.1",
130
109
  "clsx": "^2.1.1",
131
110
  "cmdk": "^1.1.1",
132
111
  "commander": "^13.1.0",
@@ -146,9 +125,11 @@
146
125
  "react-resizable-panels": "^3.0.6",
147
126
  "recharts": "^3.5.1",
148
127
  "remark-gfm": "^4.0.1",
128
+ "shadcn": "^3.6.2",
149
129
  "socket.io-client": "^4.8.1",
150
130
  "sonner": "^2.0.7",
151
131
  "tailwind-merge": "^3.4.0",
132
+ "tw-animate-css": "^1.4.0",
152
133
  "uuid": "^13.0.0",
153
134
  "vaul": "^1.1.2",
154
135
  "zod": "^4.1.13"
@@ -108,10 +108,9 @@ ${onSubmit}
108
108
 
109
109
  return (
110
110
  <Dialog open={open} onOpenChange={setOpen}>
111
- {dialogOpen === undefined && (trigger ? trigger : <CommonEditorTrigger isEdit={!!${names.camelCase}} />)}
111
+ {dialogOpen === undefined && (trigger ? <DialogTrigger>{trigger}</DialogTrigger> : <CommonEditorTrigger isEdit={!!${names.camelCase}} />)}
112
112
  <DialogContent
113
- className={\`flex max-h-[90vh] max-w-[90vw] flex-col overflow-y-auto\`}
114
- onEscapeKeyDown={(e) => e.preventDefault()}
113
+ className="flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto"
115
114
  >
116
115
  <CommonEditorHeader type={t(\`types.${names.pluralKebab}\`, { count: 1 })} name={${names.camelCase}?.name} />
117
116
  <Form {...form}>
@@ -200,7 +199,13 @@ function generateImports(data: FrontendTemplateData): string {
200
199
  componentImports.push("FormCheckbox");
201
200
  }
202
201
  if (hasRelDateFields) {
203
- componentImports.push("FormDatePicker");
202
+ componentImports.push("FormDate");
203
+ }
204
+
205
+ // Check for description/textarea fields
206
+ const hasTextareaFields = fields.some((f) => f.name === "description" || f.type === "textarea");
207
+ if (hasTextareaFields) {
208
+ componentImports.push("FormTextarea");
204
209
  }
205
210
 
206
211
  imports.push(`import {
@@ -216,7 +221,7 @@ function generateImports(data: FrontendTemplateData): string {
216
221
  imports.push(`import { Modules } from "@carlonicora/nextjs-jsonapi/core";`);
217
222
  imports.push(`import { usePageUrlGenerator } from "@carlonicora/nextjs-jsonapi/client";`);
218
223
  imports.push(`import { Action } from "@carlonicora/nextjs-jsonapi/core";`);
219
- imports.push(`import { Dialog, DialogContent, Form } from "@carlonicora/nextjs-jsonapi/components";`);
224
+ imports.push(`import { Dialog, DialogContent, DialogTrigger, Form } from "@carlonicora/nextjs-jsonapi/components";`);
220
225
 
221
226
  // Zod schema imports
222
227
  const zodSchemaImports = ["entityObjectSchema"];
@@ -529,6 +534,15 @@ function generateFormFields(data: FrontendTemplateData): string {
529
534
  bordered
530
535
  />
531
536
  </FormContainerGeneric>`);
537
+ } else if (field.name === "description" || field.type === "textarea") {
538
+ // Use FormTextarea for description and textarea fields
539
+ formElements.push(` <FormTextarea
540
+ className="h-20 min-h-20"
541
+ form={form}
542
+ id="${field.name}"
543
+ name={t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`)}
544
+ placeholder={t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.placeholder\`)}
545
+ />`);
532
546
  } else {
533
547
  const isRequired = !field.nullable;
534
548
  formElements.push(` <FormInput
@@ -580,7 +594,7 @@ function generateFormFields(data: FrontendTemplateData): string {
580
594
  break;
581
595
  case "date":
582
596
  case "datetime":
583
- formElements.push(` <FormDatePicker
597
+ formElements.push(` <FormDate
584
598
  form={form}
585
599
  id="${field.name}"
586
600
  name={t(\`features.${names.camelCase.toLowerCase()}.relationships.${fieldIdLower}.fields.${field.name}.label\`)}${isRequired ? "\n isRequired" : ""}