@carlonicora/nextjs-jsonapi 1.73.0 → 1.75.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 (105) hide show
  1. package/dist/{ApiDataInterface-DPP8s46n.d.mts → ApiDataInterface-BcZeXy5X.d.mts} +1 -0
  2. package/dist/{ApiDataInterface-DPP8s46n.d.ts → ApiDataInterface-BcZeXy5X.d.ts} +1 -0
  3. package/dist/{ApiResponseInterface-CAIAeP5d.d.ts → ApiResponseInterface-CWLvSCvS.d.ts} +1 -1
  4. package/dist/{ApiResponseInterface-zeewugD7.d.mts → ApiResponseInterface-rsXRL_Hn.d.mts} +1 -1
  5. package/dist/{AuthComponent-Di8DsZ2I.d.ts → AuthComponent-Blbs06ud.d.ts} +2 -2
  6. package/dist/{AuthComponent-DXe3kPzb.d.mts → AuthComponent-huIaK5rm.d.mts} +2 -2
  7. package/dist/{BlockNoteEditor-MZ6G4XN4.mjs → BlockNoteEditor-NJMTHPO4.mjs} +4 -4
  8. package/dist/{BlockNoteEditor-ETTYTXDX.js → BlockNoteEditor-SLT4VOLL.js} +14 -14
  9. package/dist/{BlockNoteEditor-ETTYTXDX.js.map → BlockNoteEditor-SLT4VOLL.js.map} +1 -1
  10. package/dist/{HowToInterface-NaqSG9sE.d.ts → HowToInterface-BKhnkzBp.d.ts} +1 -1
  11. package/dist/{HowToInterface-DtVWAE1s.d.mts → HowToInterface-Cj8OuQFf.d.mts} +1 -1
  12. package/dist/{ModulePathsInterface-49EWvbWy.d.mts → ModulePathsInterface-BrdqgteS.d.mts} +1 -1
  13. package/dist/{ModulePathsInterface-wVS5Raa4.d.ts → ModulePathsInterface-DJKs7s_s.d.ts} +1 -1
  14. package/dist/{auth.interface-C4uJzBec.d.mts → auth.interface-Bdq7-8iV.d.mts} +2 -2
  15. package/dist/{auth.interface-BTco8PWs.d.ts → auth.interface-CQJ6A2Cj.d.ts} +2 -2
  16. package/dist/billing/index.d.mts +3 -3
  17. package/dist/billing/index.d.ts +3 -3
  18. package/dist/billing/index.js +346 -346
  19. package/dist/billing/index.mjs +3 -3
  20. package/dist/{chunk-OPUWDWFH.js → chunk-DTE6RZXF.js} +1208 -1128
  21. package/dist/chunk-DTE6RZXF.js.map +1 -0
  22. package/dist/{chunk-QLICTZL7.js → chunk-FKLP4NED.js} +134 -129
  23. package/dist/chunk-FKLP4NED.js.map +1 -0
  24. package/dist/{chunk-HP6AJBWE.mjs → chunk-JOJZRGZL.mjs} +2 -2
  25. package/dist/{chunk-5QTDS6V7.js → chunk-OTZEXASK.js} +11 -11
  26. package/dist/{chunk-5QTDS6V7.js.map → chunk-OTZEXASK.js.map} +1 -1
  27. package/dist/{chunk-6O3YOOQM.mjs → chunk-Q7JKB777.mjs} +2383 -2303
  28. package/dist/chunk-Q7JKB777.mjs.map +1 -0
  29. package/dist/{chunk-73ANSE3J.mjs → chunk-XI35ALWY.mjs} +6 -1
  30. package/dist/chunk-XI35ALWY.mjs.map +1 -0
  31. package/dist/client/index.d.mts +10 -10
  32. package/dist/client/index.d.ts +10 -10
  33. package/dist/client/index.js +4 -4
  34. package/dist/client/index.mjs +3 -3
  35. package/dist/components/index.d.mts +39 -13
  36. package/dist/components/index.d.ts +39 -13
  37. package/dist/components/index.js +6 -4
  38. package/dist/components/index.js.map +1 -1
  39. package/dist/components/index.mjs +5 -3
  40. package/dist/{config-n0lfSf27.d.ts → config-B3jKt9P7.d.ts} +1 -1
  41. package/dist/{config-Bmr_0qTn.d.mts → config-DkHF61xA.d.mts} +1 -1
  42. package/dist/contexts/index.d.mts +5 -5
  43. package/dist/contexts/index.d.ts +5 -5
  44. package/dist/contexts/index.js +4 -4
  45. package/dist/contexts/index.mjs +3 -3
  46. package/dist/core/index.d.mts +19 -17
  47. package/dist/core/index.d.ts +19 -17
  48. package/dist/core/index.js +2 -2
  49. package/dist/core/index.mjs +1 -1
  50. package/dist/{feature.interface-CIWxo8NP.d.ts → feature.interface-BO25VLlx.d.ts} +1 -1
  51. package/dist/{feature.interface-BxFFOPNq.d.mts → feature.interface-CXb1-vNq.d.mts} +1 -1
  52. package/dist/index.d.mts +17 -17
  53. package/dist/index.d.ts +17 -17
  54. package/dist/index.js +3 -3
  55. package/dist/index.mjs +2 -2
  56. package/dist/{notification.interface-DrHu_1MM.d.mts → notification.interface-DG6obXUH.d.mts} +3 -2
  57. package/dist/{notification.interface-DYDZENx2.d.ts → notification.interface-DcSuc9CL.d.ts} +3 -2
  58. package/dist/{oauth.interface-vL7za9Bz.d.ts → oauth.interface-B6xmfDzK.d.ts} +1 -1
  59. package/dist/{oauth.interface-DsZ5ecSX.d.mts → oauth.interface-o5FLpiN7.d.mts} +1 -1
  60. package/dist/{s3.service-TsN2unZr.d.mts → s3.service-DGilbikH.d.mts} +4 -4
  61. package/dist/{s3.service-DK2KKXbR.d.ts → s3.service-DjwEQJPe.d.ts} +4 -4
  62. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -1
  63. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +14 -120
  64. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -1
  65. package/dist/server/index.d.mts +6 -6
  66. package/dist/server/index.d.ts +6 -6
  67. package/dist/server/index.js +3 -3
  68. package/dist/server/index.mjs +1 -1
  69. package/dist/{stripe-promotion-code.interface-BcJty0rv.d.ts → stripe-promotion-code.interface-C3qqh3mi.d.ts} +2 -2
  70. package/dist/{stripe-promotion-code.interface-Dnm2DJKQ.d.mts → stripe-promotion-code.interface-ClZ7DxS9.d.mts} +2 -2
  71. package/dist/testing/index.d.mts +2 -2
  72. package/dist/testing/index.d.ts +2 -2
  73. package/dist/testing/index.js +5 -1
  74. package/dist/testing/index.js.map +1 -1
  75. package/dist/testing/index.mjs +5 -1
  76. package/dist/testing/index.mjs.map +1 -1
  77. package/dist/{useRbacState-BYaSdA78.d.ts → useRbacState-C88O-5L8.d.ts} +3 -3
  78. package/dist/{useRbacState-CQEJ_ysV.d.mts → useRbacState-mqYiRp3J.d.mts} +3 -3
  79. package/dist/{useSocket-Cjt_qvkI.d.ts → useSocket-8eUtnL7J.d.ts} +1 -1
  80. package/dist/{useSocket-VAGetcT3.d.mts → useSocket-CmzVtg32.d.mts} +1 -1
  81. package/package.json +1 -1
  82. package/scripts/generate-web-module/templates/components/multi-selector.template.ts +14 -120
  83. package/src/client/hooks/__tests__/useRehydration.test.ts +3 -0
  84. package/src/components/forms/EntityMultiSelector.tsx +325 -0
  85. package/src/components/forms/index.ts +1 -0
  86. package/src/components/navigations/RecentPagesNavigator.tsx +16 -13
  87. package/src/core/abstracts/AbstractApiData.ts +10 -0
  88. package/src/core/abstracts/__tests__/identifier.spec.ts +117 -0
  89. package/src/core/factories/__tests__/JsonApiDataFactory.test.ts +3 -0
  90. package/src/core/factories/__tests__/RehydrationFactory.test.ts +3 -0
  91. package/src/core/interfaces/ApiDataInterface.ts +1 -0
  92. package/src/core/registry/__tests__/DataClassRegistry.getByJsonApiType.spec.ts +6 -0
  93. package/src/core/registry/__tests__/DataClassRegistry.test.ts +3 -0
  94. package/src/core/registry/__tests__/ModuleRegistrar.test.ts +6 -0
  95. package/src/features/how-to/components/forms/HowToMultiSelector.tsx +14 -120
  96. package/src/features/rbac/hooks/useRbacState.test.ts +2 -0
  97. package/src/features/user/components/forms/UserMultiSelect.tsx +34 -181
  98. package/src/permissions/types.ts +1 -0
  99. package/src/testing/factories/createMockApiData.ts +7 -0
  100. package/dist/chunk-6O3YOOQM.mjs.map +0 -1
  101. package/dist/chunk-73ANSE3J.mjs.map +0 -1
  102. package/dist/chunk-OPUWDWFH.js.map +0 -1
  103. package/dist/chunk-QLICTZL7.js.map +0 -1
  104. /package/dist/{BlockNoteEditor-MZ6G4XN4.mjs.map → BlockNoteEditor-NJMTHPO4.mjs.map} +0 -0
  105. /package/dist/{chunk-HP6AJBWE.mjs.map → chunk-JOJZRGZL.mjs.map} +0 -0
@@ -1,7 +1,7 @@
1
- import { F as FeatureInterface } from './feature.interface-BxFFOPNq.mjs';
2
- import { R as RoleInterface, U as UserInterface, m as UserInput, j as CompanyInterface, i as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-DrHu_1MM.mjs';
3
- import { A as ApiDataInterface, J as JsonApiHydratedDataInterface } from './ApiDataInterface-DPP8s46n.mjs';
4
- import { A as AuthInput, b as AuthInterface } from './auth.interface-C4uJzBec.mjs';
1
+ import { F as FeatureInterface } from './feature.interface-CXb1-vNq.mjs';
2
+ import { R as RoleInterface, U as UserInterface, m as UserInput, j as CompanyInterface, i as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-DG6obXUH.mjs';
3
+ import { A as ApiDataInterface, J as JsonApiHydratedDataInterface } from './ApiDataInterface-BcZeXy5X.mjs';
4
+ import { A as AuthInput, b as AuthInterface } from './auth.interface-Bdq7-8iV.mjs';
5
5
  import { A as ApiRequestDataTypeInterface } from './ApiRequestDataTypeInterface-CYEcRUrh.mjs';
6
6
 
7
7
  declare enum HttpMethod {
@@ -1,7 +1,7 @@
1
- import { F as FeatureInterface } from './feature.interface-CIWxo8NP.js';
2
- import { R as RoleInterface, U as UserInterface, m as UserInput, j as CompanyInterface, i as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-DYDZENx2.js';
3
- import { A as ApiDataInterface, J as JsonApiHydratedDataInterface } from './ApiDataInterface-DPP8s46n.js';
4
- import { A as AuthInput, b as AuthInterface } from './auth.interface-BTco8PWs.js';
1
+ import { F as FeatureInterface } from './feature.interface-BO25VLlx.js';
2
+ import { R as RoleInterface, U as UserInterface, m as UserInput, j as CompanyInterface, i as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-DcSuc9CL.js';
3
+ import { A as ApiDataInterface, J as JsonApiHydratedDataInterface } from './ApiDataInterface-BcZeXy5X.js';
4
+ import { A as AuthInput, b as AuthInterface } from './auth.interface-CQJ6A2Cj.js';
5
5
  import { A as ApiRequestDataTypeInterface } from './ApiRequestDataTypeInterface-CYEcRUrh.js';
6
6
 
7
7
  declare enum HttpMethod {
@@ -1 +1 @@
1
- {"version":3,"file":"multi-selector.template.d.ts","sourceRoot":"","sources":["../../../../../scripts/generate-web-module/templates/components/multi-selector.template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CA+JhF"}
1
+ {"version":3,"file":"multi-selector.template.d.ts","sourceRoot":"","sources":["../../../../../scripts/generate-web-module/templates/components/multi-selector.template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CAqDhF"}
@@ -21,18 +21,9 @@ function generateMultiSelectorTemplate(data) {
21
21
 
22
22
  import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
23
23
  import { ${names.pascalCase}Service } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
24
- import { DataListRetriever, useDataListRetriever, useDebounce } from "@carlonicora/nextjs-jsonapi/client";
25
- import { FormFieldWrapper, MultipleSelector } from "@carlonicora/nextjs-jsonapi/components";
26
- import { Option } from "@carlonicora/nextjs-jsonapi/components";
24
+ import { EntityMultiSelector } from "@carlonicora/nextjs-jsonapi/components";
27
25
  import { Modules } from "@carlonicora/nextjs-jsonapi/core";
28
- import { useCallback, useEffect, useMemo, useState } from "react";
29
26
  import { useTranslations } from "next-intl";
30
- import { useWatch } from "react-hook-form";
31
-
32
- type ${names.pascalCase}MultiSelectType = {
33
- id: string;
34
- ${displayProp}: string;
35
- };
36
27
 
37
28
  type ${names.pascalCase}MultiSelectorProps = {
38
29
  id: string;
@@ -45,10 +36,6 @@ type ${names.pascalCase}MultiSelectorProps = {
45
36
  isRequired?: boolean;
46
37
  };
47
38
 
48
- type ${names.pascalCase}Option = Option & {
49
- ${names.camelCase}Data?: ${names.pascalCase}Interface;
50
- };
51
-
52
39
  export default function ${names.pascalCase}MultiSelector({
53
40
  id,
54
41
  form,
@@ -56,117 +43,24 @@ export default function ${names.pascalCase}MultiSelector({
56
43
  label,
57
44
  placeholder,
58
45
  onChange,
59
- maxCount = 3,
60
46
  isRequired = false,
61
47
  }: ${names.pascalCase}MultiSelectorProps) {
62
48
  const t = useTranslations();
63
- const [${names.camelCase}Options, set${names.pascalCase}Options] = useState<${names.pascalCase}Option[]>([]);
64
- const [searchTerm, setSearchTerm] = useState<string>("");
65
-
66
- const selected${names.pluralPascal}: ${names.pascalCase}MultiSelectType[] = useWatch({ control: form.control, name: id }) || [];
67
-
68
- const data: DataListRetriever<${names.pascalCase}Interface> = useDataListRetriever({
69
- retriever: (params) => ${names.pascalCase}Service.findMany(params),
70
- retrieverParams: {},
71
- ready: true,
72
- module: Modules.${names.pascalCase},
73
- });
74
-
75
- const updateSearch = useCallback(
76
- (searchedTerm: string) => {
77
- if (searchedTerm.trim()) {
78
- data.addAdditionalParameter("search", searchedTerm.trim());
79
- } else {
80
- data.removeAdditionalParameter("search");
81
- }
82
- },
83
- [data]
84
- );
85
-
86
- const debouncedUpdateSearch = useDebounce(updateSearch, 500);
87
-
88
- useEffect(() => {
89
- debouncedUpdateSearch(searchTerm);
90
- }, [debouncedUpdateSearch, searchTerm]);
91
-
92
- useEffect(() => {
93
- if (data.data && data.data.length > 0) {
94
- const ${names.pluralCamel} = data.data as ${names.pascalCase}Interface[];
95
- const filtered${names.pluralPascal} = ${names.pluralCamel}.filter((${names.camelCase}) => ${names.camelCase}.id !== current${names.pascalCase}?.id);
96
-
97
- const options: ${names.pascalCase}Option[] = filtered${names.pluralPascal}.map((${names.camelCase}) => ({
98
- label: ${names.camelCase}.${displayProp},
99
- value: ${names.camelCase}.id,
100
- ${names.camelCase}Data: ${names.camelCase},
101
- }));
102
-
103
- // Add options for any already selected that aren't in search results
104
- const existingOptionIds = new Set(options.map((option) => option.value));
105
- const missingOptions: ${names.pascalCase}Option[] = selected${names.pluralPascal}
106
- .filter((${names.camelCase}) => !existingOptionIds.has(${names.camelCase}.id))
107
- .map((${names.camelCase}) => ({
108
- label: ${names.camelCase}.${displayProp},
109
- value: ${names.camelCase}.id,
110
- ${names.camelCase}Data: ${names.camelCase} as unknown as ${names.pascalCase}Interface,
111
- }));
112
-
113
- set${names.pascalCase}Options([...options, ...missingOptions]);
114
- }
115
- }, [data.data, current${names.pascalCase}, selected${names.pluralPascal}]);
116
-
117
- // Convert selected to Option[] format
118
- const selectedOptions = useMemo(() => {
119
- return selected${names.pluralPascal}.map((${names.camelCase}) => ({
120
- value: ${names.camelCase}.id,
121
- label: ${names.camelCase}.${displayProp},
122
- }));
123
- }, [selected${names.pluralPascal}]);
124
-
125
- const handleChange = (options: Option[]) => {
126
- // Convert to form format
127
- const formValues = options.map((option) => ({
128
- id: option.value,
129
- ${displayProp}: option.label,
130
- }));
131
-
132
- form.setValue(id, formValues, { shouldDirty: true, shouldTouch: true });
133
-
134
- if (onChange) {
135
- // Get full data for onChange callback
136
- const fullData = options
137
- .map((option) => {
138
- const ${names.camelCase}Option = ${names.camelCase}Options.find((opt) => opt.value === option.value);
139
- return ${names.camelCase}Option?.${names.camelCase}Data;
140
- })
141
- .filter(Boolean) as ${names.pascalCase}Interface[];
142
- onChange(fullData);
143
- }
144
- };
145
-
146
- // Search handler
147
- const handleSearchSync = (search: string): Option[] => {
148
- setSearchTerm(search);
149
- return ${names.camelCase}Options;
150
- };
151
49
 
152
50
  return (
153
- <div className="flex w-full flex-col">
154
- <FormFieldWrapper form={form} name={id} label={label} isRequired={isRequired}>
155
- {() => (
156
- <MultipleSelector
157
- value={selectedOptions}
158
- onChange={handleChange}
159
- options={${names.camelCase}Options}
160
- placeholder={placeholder}
161
- maxDisplayCount={maxCount}
162
- hideClearAllButton
163
- onSearchSync={handleSearchSync}
164
- delay={0}
165
- emptyIndicator={<span className="text-muted-foreground">{t("ui.search.no_results_generic")}</span>}
166
- />
167
- )}
168
- </FormFieldWrapper>
169
- </div>
51
+ <EntityMultiSelector<${names.pascalCase}Interface>
52
+ id={id}
53
+ form={form}
54
+ label={label}
55
+ placeholder={placeholder || t("ui.search.button")}
56
+ emptyText={t("ui.search.no_results_generic")}
57
+ isRequired={isRequired}
58
+ retriever={(params) => ${names.pascalCase}Service.findMany(params)}
59
+ module={Modules.${names.pascalCase}}
60
+ getLabel={(${names.camelCase}) => ${displayProp === "id" ? `${names.camelCase}.id` : `${names.camelCase}.${displayProp}`}}
61
+ excludeId={current${names.pascalCase}?.id}
62
+ onChange={onChange}
63
+ />
170
64
  );
171
65
  }
172
66
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"multi-selector.template.js","sourceRoot":"","sources":["../../../../../scripts/generate-web-module/templates/components/multi-selector.template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"multi-selector.template.js","sourceRoot":"","sources":["../../../../../scripts/generate-web-module/templates/components/multi-selector.template.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAUH,sEAqDC;AA3DD;;;;;GAKG;AACH,SAAgB,6BAA6B,CAAC,IAA0B;IACtE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAC/C,MAAM,YAAY,GAAG,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAC7E,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC;IACnG,MAAM,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC;IAE7E,OAAO;;WAEE,KAAK,CAAC,UAAU,gCAAgC,IAAI,CAAC,eAAe,IAAI,KAAK,CAAC,SAAS,SAAS,KAAK,CAAC,UAAU;WAChH,KAAK,CAAC,UAAU,8BAA8B,IAAI,CAAC,eAAe,IAAI,KAAK,CAAC,SAAS,SAAS,KAAK,CAAC,UAAU;;;;;OAKlH,KAAK,CAAC,UAAU;;;WAGZ,KAAK,CAAC,UAAU,MAAM,KAAK,CAAC,UAAU;;;gBAGjC,KAAK,CAAC,WAAW,MAAM,KAAK,CAAC,UAAU;;;;;0BAK7B,KAAK,CAAC,UAAU;;;WAG/B,KAAK,CAAC,UAAU;;;;;KAKtB,KAAK,CAAC,UAAU;;;;2BAIM,KAAK,CAAC,UAAU;;;;;;;+BAOZ,KAAK,CAAC,UAAU;wBACvB,KAAK,CAAC,UAAU;mBACrB,KAAK,CAAC,SAAS,QAAQ,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,IAAI,WAAW,EAAE;0BACpG,KAAK,CAAC,UAAU;;;;;CAKzC,CAAC;AACF,CAAC"}
@@ -1,12 +1,12 @@
1
1
  import { A as ApiData } from '../ApiData-DPKNfY-9.mjs';
2
- import { M as ModuleWithPermissions, A as Action } from '../notification.interface-DrHu_1MM.mjs';
2
+ import { M as ModuleWithPermissions, A as Action } from '../notification.interface-DG6obXUH.mjs';
3
3
  import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.mjs';
4
- import { A as ApiResponseInterface } from '../ApiResponseInterface-zeewugD7.mjs';
5
- export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-TsN2unZr.mjs';
4
+ import { A as ApiResponseInterface } from '../ApiResponseInterface-rsXRL_Hn.mjs';
5
+ export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-DGilbikH.mjs';
6
6
  import 'lucide-react';
7
- import '../ApiDataInterface-DPP8s46n.mjs';
8
- import '../feature.interface-BxFFOPNq.mjs';
9
- import '../auth.interface-C4uJzBec.mjs';
7
+ import '../ApiDataInterface-BcZeXy5X.mjs';
8
+ import '../feature.interface-CXb1-vNq.mjs';
9
+ import '../auth.interface-Bdq7-8iV.mjs';
10
10
 
11
11
  type CacheProfile = "seconds" | "minutes" | "hours" | "days" | "weeks" | "max" | "default";
12
12
  /**
@@ -1,12 +1,12 @@
1
1
  import { A as ApiData } from '../ApiData-DPKNfY-9.js';
2
- import { M as ModuleWithPermissions, A as Action } from '../notification.interface-DYDZENx2.js';
2
+ import { M as ModuleWithPermissions, A as Action } from '../notification.interface-DcSuc9CL.js';
3
3
  import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.js';
4
- import { A as ApiResponseInterface } from '../ApiResponseInterface-CAIAeP5d.js';
5
- export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-DK2KKXbR.js';
4
+ import { A as ApiResponseInterface } from '../ApiResponseInterface-CWLvSCvS.js';
5
+ export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-DjwEQJPe.js';
6
6
  import 'lucide-react';
7
- import '../ApiDataInterface-DPP8s46n.js';
8
- import '../feature.interface-CIWxo8NP.js';
9
- import '../auth.interface-BTco8PWs.js';
7
+ import '../ApiDataInterface-BcZeXy5X.js';
8
+ import '../feature.interface-BO25VLlx.js';
9
+ import '../auth.interface-CQJ6A2Cj.js';
10
10
 
11
11
  type CacheProfile = "seconds" | "minutes" | "hours" | "days" | "weeks" | "max" | "default";
12
12
  /**
@@ -15,7 +15,7 @@ var _chunk3ZPK4QOBjs = require('../chunk-3ZPK4QOB.js');
15
15
 
16
16
 
17
17
 
18
- var _chunkQLICTZL7js = require('../chunk-QLICTZL7.js');
18
+ var _chunkFKLP4NEDjs = require('../chunk-FKLP4NED.js');
19
19
  require('../chunk-LXKSUWAV.js');
20
20
  require('../chunk-IBS6NI7D.js');
21
21
 
@@ -86,7 +86,7 @@ var ServerSession = class {
86
86
  if (!rawModules) return false;
87
87
  const modules = JSON.parse(_pako2.default.ungzip(Buffer.from(rawModules, "base64"), { to: "string" }));
88
88
  const selectedModule = modules.find((module) => module.id === params.module.moduleId);
89
- return _chunkQLICTZL7js.checkPermissionsFromServer.call(void 0, {
89
+ return _chunkFKLP4NEDjs.checkPermissionsFromServer.call(void 0, {
90
90
  module: params.module,
91
91
  action: params.action,
92
92
  data: params.data,
@@ -296,5 +296,5 @@ _chunk7QVYU63Ejs.__name.call(void 0, ServerJsonApiDelete, "ServerJsonApiDelete")
296
296
 
297
297
 
298
298
 
299
- exports.ServerAuthService = _chunkQLICTZL7js.AuthService; exports.ServerCompanyService = _chunkQLICTZL7js.CompanyService; exports.ServerContentService = _chunkQLICTZL7js.ContentService; exports.ServerFeatureService = _chunkQLICTZL7js.FeatureService; exports.ServerJsonApiDelete = ServerJsonApiDelete; exports.ServerJsonApiGet = ServerJsonApiGet; exports.ServerJsonApiPatch = ServerJsonApiPatch; exports.ServerJsonApiPost = ServerJsonApiPost; exports.ServerJsonApiPut = ServerJsonApiPut; exports.ServerNotificationService = _chunkQLICTZL7js.NotificationService; exports.ServerPushService = _chunkQLICTZL7js.PushService; exports.ServerRoleService = _chunkQLICTZL7js.RoleService; exports.ServerS3Service = _chunkQLICTZL7js.S3Service; exports.ServerSession = ServerSession; exports.ServerUserService = _chunkQLICTZL7js.UserService; exports.configureServerJsonApi = configureServerJsonApi; exports.getServerApiUrl = getServerApiUrl; exports.getServerAppUrl = getServerAppUrl; exports.getServerToken = _chunkYUO55Q5Ajs.getServerToken; exports.getServerTrackablePages = getServerTrackablePages; exports.invalidateCacheTag = invalidateCacheTag; exports.invalidateCacheTags = invalidateCacheTags; exports.serverRequest = _chunk3ZPK4QOBjs.serverRequest;
299
+ exports.ServerAuthService = _chunkFKLP4NEDjs.AuthService; exports.ServerCompanyService = _chunkFKLP4NEDjs.CompanyService; exports.ServerContentService = _chunkFKLP4NEDjs.ContentService; exports.ServerFeatureService = _chunkFKLP4NEDjs.FeatureService; exports.ServerJsonApiDelete = ServerJsonApiDelete; exports.ServerJsonApiGet = ServerJsonApiGet; exports.ServerJsonApiPatch = ServerJsonApiPatch; exports.ServerJsonApiPost = ServerJsonApiPost; exports.ServerJsonApiPut = ServerJsonApiPut; exports.ServerNotificationService = _chunkFKLP4NEDjs.NotificationService; exports.ServerPushService = _chunkFKLP4NEDjs.PushService; exports.ServerRoleService = _chunkFKLP4NEDjs.RoleService; exports.ServerS3Service = _chunkFKLP4NEDjs.S3Service; exports.ServerSession = ServerSession; exports.ServerUserService = _chunkFKLP4NEDjs.UserService; exports.configureServerJsonApi = configureServerJsonApi; exports.getServerApiUrl = getServerApiUrl; exports.getServerAppUrl = getServerAppUrl; exports.getServerToken = _chunkYUO55Q5Ajs.getServerToken; exports.getServerTrackablePages = getServerTrackablePages; exports.invalidateCacheTag = invalidateCacheTag; exports.invalidateCacheTags = invalidateCacheTags; exports.serverRequest = _chunk3ZPK4QOBjs.serverRequest;
300
300
  //# sourceMappingURL=index.js.map
@@ -15,7 +15,7 @@ import {
15
15
  S3Service,
16
16
  UserService,
17
17
  checkPermissionsFromServer
18
- } from "../chunk-73ANSE3J.mjs";
18
+ } from "../chunk-XI35ALWY.mjs";
19
19
  import "../chunk-AUXK7QSA.mjs";
20
20
  import "../chunk-C7C7VY4F.mjs";
21
21
  import {
@@ -1,5 +1,5 @@
1
- import { A as ApiDataInterface } from './ApiDataInterface-DPP8s46n.js';
2
- import { F as FeatureInterface } from './feature.interface-CIWxo8NP.js';
1
+ import { A as ApiDataInterface } from './ApiDataInterface-BcZeXy5X.js';
2
+ import { F as FeatureInterface } from './feature.interface-BO25VLlx.js';
3
3
 
4
4
  interface StripeUsageInterface extends ApiDataInterface {
5
5
  get subscriptionId(): string;
@@ -1,5 +1,5 @@
1
- import { A as ApiDataInterface } from './ApiDataInterface-DPP8s46n.mjs';
2
- import { F as FeatureInterface } from './feature.interface-BxFFOPNq.mjs';
1
+ import { A as ApiDataInterface } from './ApiDataInterface-BcZeXy5X.mjs';
2
+ import { F as FeatureInterface } from './feature.interface-CXb1-vNq.mjs';
3
3
 
4
4
  interface StripeUsageInterface extends ApiDataInterface {
5
5
  get subscriptionId(): string;
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React__default, { ReactElement } from 'react';
3
3
  import { J as JsonApiConfig } from '../JsonApiContext-Bsm_Q2oe.mjs';
4
4
  import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.mjs';
5
- import { A as ApiResponseInterface } from '../ApiResponseInterface-zeewugD7.mjs';
6
- import { A as ApiDataInterface } from '../ApiDataInterface-DPP8s46n.mjs';
5
+ import { A as ApiResponseInterface } from '../ApiResponseInterface-rsXRL_Hn.mjs';
6
+ import { A as ApiDataInterface } from '../ApiDataInterface-BcZeXy5X.mjs';
7
7
  import { Mock } from 'vitest';
8
8
  import { RenderOptions, RenderResult } from '@testing-library/react';
9
9
  export { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React__default, { ReactElement } from 'react';
3
3
  import { J as JsonApiConfig } from '../JsonApiContext-Bsm_Q2oe.js';
4
4
  import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.js';
5
- import { A as ApiResponseInterface } from '../ApiResponseInterface-CAIAeP5d.js';
6
- import { A as ApiDataInterface } from '../ApiDataInterface-DPP8s46n.js';
5
+ import { A as ApiResponseInterface } from '../ApiResponseInterface-CWLvSCvS.js';
6
+ import { A as ApiDataInterface } from '../ApiDataInterface-BcZeXy5X.js';
7
7
  import { Mock } from 'vitest';
8
8
  import { RenderOptions, RenderResult } from '@testing-library/react';
9
9
  export { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
@@ -180,7 +180,11 @@ function createMockApiData(options) {
180
180
  type,
181
181
  id,
182
182
  attributes: data
183
- }), "createJsonApi")
183
+ }), "createJsonApi"),
184
+ get identifier() {
185
+ const identifierFields = ["name"];
186
+ return identifierFields.map((field) => attributes[field]).filter((v) => v != null && v !== "").join(" ");
187
+ }
184
188
  };
185
189
  Object.keys(attributes).forEach((key) => {
186
190
  Object.defineProperty(mockData, key, {
@@ -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 <JsonApiContext.Provider value={mergedConfig}>{children}</JsonApiContext.Provider>;\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 {\n MockJsonApiProvider,\n MockJsonApiProviderProps as _MockJsonApiProviderProps,\n} 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(ui: ReactElement, options: RenderWithProvidersOptions = {}): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = <MockJsonApiProvider config={jsonApiConfig}>{children}</MockJsonApiProvider>;\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 <JsonApiContext.Provider value={mergedConfig}>{children}</JsonApiContext.Provider>;\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 get identifier() {\n const identifierFields = [\"name\"];\n return identifierFields\n .map((field) => attributes[field])\n .filter((v) => v != null && v !== \"\")\n .join(\" \");\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 {\n MockJsonApiProvider,\n MockJsonApiProviderProps as _MockJsonApiProviderProps,\n} 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(ui: ReactElement, options: RenderWithProvidersOptions = {}): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = <MockJsonApiProvider config={jsonApiConfig}>{children}</MockJsonApiProvider>;\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"]}
@@ -180,7 +180,11 @@ function createMockApiData(options) {
180
180
  type,
181
181
  id,
182
182
  attributes: data
183
- }), "createJsonApi")
183
+ }), "createJsonApi"),
184
+ get identifier() {
185
+ const identifierFields = ["name"];
186
+ return identifierFields.map((field) => attributes[field]).filter((v) => v != null && v !== "").join(" ");
187
+ }
184
188
  };
185
189
  Object.keys(attributes).forEach((key) => {
186
190
  Object.defineProperty(mockData, key, {
@@ -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 <JsonApiContext.Provider value={mergedConfig}>{children}</JsonApiContext.Provider>;\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 {\n MockJsonApiProvider,\n MockJsonApiProviderProps as _MockJsonApiProviderProps,\n} 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(ui: ReactElement, options: RenderWithProvidersOptions = {}): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = <MockJsonApiProvider config={jsonApiConfig}>{children}</MockJsonApiProvider>;\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 <JsonApiContext.Provider value={mergedConfig}>{children}</JsonApiContext.Provider>;\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 get identifier() {\n const identifierFields = [\"name\"];\n return identifierFields\n .map((field) => attributes[field])\n .filter((v) => v != null && v !== \"\")\n .join(\" \");\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 {\n MockJsonApiProvider,\n MockJsonApiProviderProps as _MockJsonApiProviderProps,\n} 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(ui: ReactElement, options: RenderWithProvidersOptions = {}): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = <MockJsonApiProvider config={jsonApiConfig}>{children}</MockJsonApiProvider>;\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"]}