@intellegens/cornerstone-client 0.0.9999-alpha-8 → 0.0.9999-alpha-10

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 (227) hide show
  1. package/demo/index.ts +29 -0
  2. package/demo/public_html/favicon.ico +0 -0
  3. package/demo/public_html/index.html +106 -0
  4. package/demo/public_html/websettings.json +3 -0
  5. package/{adapters → dist/adapters}/CollectionViewAdapter/index.d.ts +0 -0
  6. package/{adapters → dist/adapters}/CollectionViewAdapter/index.js +0 -0
  7. package/{adapters → dist/adapters}/index.d.ts +0 -0
  8. package/{adapters → dist/adapters}/index.js +0 -0
  9. package/{data → dist/data}/api/dto/PropertyPathDto.d.ts +0 -0
  10. package/{data → dist/data}/api/dto/PropertyPathDto.js +0 -0
  11. package/{data → dist/data}/api/dto/ReadOptionsDto.d.ts +0 -0
  12. package/{data → dist/data}/api/dto/ReadOptionsDto.js +0 -0
  13. package/{data → dist/data}/api/dto/ReadResultDto.d.ts +0 -0
  14. package/{data → dist/data}/api/dto/ReadResultDto.js +0 -0
  15. package/{data → dist/data}/api/dto/ReadResultMetadataDto.d.ts +0 -0
  16. package/{data → dist/data}/api/dto/ReadResultMetadataDto.js +0 -0
  17. package/{data → dist/data}/api/dto/crud/CrudMetadataDto.d.ts +0 -0
  18. package/{data → dist/data}/api/dto/crud/CrudMetadataDto.js +0 -0
  19. package/{data → dist/data}/api/dto/crud/index.d.ts +0 -0
  20. package/{data → dist/data}/api/dto/crud/index.js +0 -0
  21. package/{data → dist/data}/api/dto/index.d.ts +0 -0
  22. package/{data → dist/data}/api/dto/index.js +0 -0
  23. package/{data → dist/data}/api/dto/read/ReadMetadataDto.d.ts +0 -0
  24. package/{data → dist/data}/api/dto/read/ReadMetadataDto.js +0 -0
  25. package/{data → dist/data}/api/dto/read/ReadSelectedDefinitionDto.d.ts +0 -0
  26. package/{data → dist/data}/api/dto/read/ReadSelectedDefinitionDto.js +0 -0
  27. package/{data → dist/data}/api/dto/read/ReadSelectedOrderingDefinitionDto.d.ts +0 -0
  28. package/{data → dist/data}/api/dto/read/ReadSelectedOrderingDefinitionDto.js +0 -0
  29. package/{data → dist/data}/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.d.ts +0 -0
  30. package/{data → dist/data}/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.js +0 -0
  31. package/{data → dist/data}/api/dto/read/ReadSelectedPaginationDefinitionDto.d.ts +0 -0
  32. package/{data → dist/data}/api/dto/read/ReadSelectedPaginationDefinitionDto.js +0 -0
  33. package/{data → dist/data}/api/dto/read/ReadSelectedSearchDefinitionDto.d.ts +0 -0
  34. package/{data → dist/data}/api/dto/read/ReadSelectedSearchDefinitionDto.js +0 -0
  35. package/{data → dist/data}/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.d.ts +0 -0
  36. package/{data → dist/data}/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.js +0 -0
  37. package/{data → dist/data}/api/dto/read/index.d.ts +0 -0
  38. package/{data → dist/data}/api/dto/read/index.js +0 -0
  39. package/{data → dist/data}/api/dto/response/ApiErrorDto.d.ts +0 -0
  40. package/{data → dist/data}/api/dto/response/ApiErrorDto.js +0 -0
  41. package/{data → dist/data}/api/dto/response/ApiResponseDto.d.ts +0 -0
  42. package/{data → dist/data}/api/dto/response/ApiResponseDto.js +0 -0
  43. package/{data → dist/data}/api/dto/response/ApiSuccessResponseDto.d.ts +0 -0
  44. package/{data → dist/data}/api/dto/response/ApiSuccessResponseDto.js +0 -0
  45. package/{data → dist/data}/api/dto/response/EmptyMetadataDto.d.ts +0 -0
  46. package/{data → dist/data}/api/dto/response/EmptyMetadataDto.js +0 -0
  47. package/{data → dist/data}/api/dto/response/index.d.ts +0 -0
  48. package/{data → dist/data}/api/dto/response/index.js +0 -0
  49. package/{data → dist/data}/api/enum/index.d.ts +0 -0
  50. package/{data → dist/data}/api/enum/index.js +0 -0
  51. package/{data → dist/data}/api/enum/read/ReadSelectedComparisonOperator.d.ts +0 -0
  52. package/{data → dist/data}/api/enum/read/ReadSelectedComparisonOperator.js +0 -0
  53. package/{data → dist/data}/api/enum/read/ReadSelectedLogicalOperator.d.ts +0 -0
  54. package/{data → dist/data}/api/enum/read/ReadSelectedLogicalOperator.js +0 -0
  55. package/{data → dist/data}/api/enum/read/ReadSelectedOrderingDirection.d.ts +0 -0
  56. package/{data → dist/data}/api/enum/read/ReadSelectedOrderingDirection.js +0 -0
  57. package/{data → dist/data}/api/enum/read/ReadSelectedPropertyType.d.ts +0 -0
  58. package/{data → dist/data}/api/enum/read/ReadSelectedPropertyType.js +0 -0
  59. package/{data → dist/data}/api/enum/read/index.d.ts +0 -0
  60. package/{data → dist/data}/api/enum/read/index.js +0 -0
  61. package/{data → dist/data}/api/enum/response/ApiErrorCodes.d.ts +0 -0
  62. package/{data → dist/data}/api/enum/response/ApiErrorCodes.js +0 -0
  63. package/{data → dist/data}/api/enum/response/index.d.ts +0 -0
  64. package/{data → dist/data}/api/enum/response/index.js +0 -0
  65. package/{data → dist/data}/api/index.d.ts +0 -0
  66. package/{data → dist/data}/api/index.js +0 -0
  67. package/{data → dist/data}/api/interface/IConcurrencySafe.d.ts +0 -0
  68. package/{data → dist/data}/api/interface/IConcurrencySafe.js +0 -0
  69. package/{data → dist/data}/api/interface/IIdentifiable.d.ts +0 -0
  70. package/{data → dist/data}/api/interface/IIdentifiable.js +0 -0
  71. package/{data → dist/data}/api/interface/IIdentifiableSecondary.d.ts +0 -0
  72. package/{data → dist/data}/api/interface/IIdentifiableSecondary.js +0 -0
  73. package/{data → dist/data}/api/interface/index.d.ts +0 -0
  74. package/{data → dist/data}/api/interface/index.js +0 -0
  75. package/{data → dist/data}/auth/dto/ClaimDto.d.ts +0 -0
  76. package/{data → dist/data}/auth/dto/ClaimDto.js +0 -0
  77. package/{data → dist/data}/auth/dto/RegisterRequestDto.d.ts +0 -0
  78. package/{data → dist/data}/auth/dto/RegisterRequestDto.js +0 -0
  79. package/{data → dist/data}/auth/dto/RoleDto.d.ts +0 -0
  80. package/{data → dist/data}/auth/dto/RoleDto.js +0 -0
  81. package/{data → dist/data}/auth/dto/SignInRequestDto.d.ts +0 -0
  82. package/{data → dist/data}/auth/dto/SignInRequestDto.js +0 -0
  83. package/{data → dist/data}/auth/dto/TokensDto.d.ts +0 -0
  84. package/{data → dist/data}/auth/dto/TokensDto.js +0 -0
  85. package/{data → dist/data}/auth/dto/UserDto.d.ts +0 -0
  86. package/{data → dist/data}/auth/dto/UserDto.js +0 -0
  87. package/{data → dist/data}/auth/dto/UserInfoDto.d.ts +0 -0
  88. package/{data → dist/data}/auth/dto/UserInfoDto.js +0 -0
  89. package/{data → dist/data}/auth/dto/index.d.ts +0 -0
  90. package/{data → dist/data}/auth/dto/index.js +0 -0
  91. package/{data → dist/data}/auth/index.d.ts +0 -0
  92. package/{data → dist/data}/auth/index.js +0 -0
  93. package/{data → dist/data}/index.d.ts +0 -0
  94. package/{data → dist/data}/index.js +0 -0
  95. package/{index.d.ts → dist/index.d.ts} +0 -0
  96. package/{index.js → dist/index.js} +0 -0
  97. package/{services → dist/services}/api/ApiCrudControllerClient/index.d.ts +0 -0
  98. package/{services → dist/services}/api/ApiCrudControllerClient/index.js +0 -0
  99. package/{services → dist/services}/api/ApiInitializationService/index.d.ts +0 -0
  100. package/{services → dist/services}/api/ApiInitializationService/index.js +0 -0
  101. package/{services → dist/services}/api/ApiReadControllerClient/index.d.ts +0 -0
  102. package/{services → dist/services}/api/ApiReadControllerClient/index.js +0 -0
  103. package/{services → dist/services}/api/HttpService/FetchHttpService.d.ts +0 -0
  104. package/{services → dist/services}/api/HttpService/FetchHttpService.js +0 -0
  105. package/{services → dist/services}/api/HttpService/HttpRequestConfig.d.ts +0 -0
  106. package/{services → dist/services}/api/HttpService/HttpRequestConfig.js +0 -0
  107. package/{services → dist/services}/api/HttpService/HttpResponse.d.ts +0 -0
  108. package/{services → dist/services}/api/HttpService/HttpResponse.js +0 -0
  109. package/{services → dist/services}/api/HttpService/IHttpService.d.ts +0 -0
  110. package/{services → dist/services}/api/HttpService/IHttpService.js +0 -0
  111. package/{services → dist/services}/api/HttpService/index.d.ts +0 -0
  112. package/{services → dist/services}/api/HttpService/index.js +0 -0
  113. package/{services → dist/services}/api/UserManagementControllerClient/index.d.ts +0 -0
  114. package/{services → dist/services}/api/UserManagementControllerClient/index.js +0 -0
  115. package/{services → dist/services}/api/index.d.ts +0 -0
  116. package/{services → dist/services}/api/index.js +0 -0
  117. package/{services → dist/services}/auth/client/AuthService/index.d.ts +0 -0
  118. package/{services → dist/services}/auth/client/AuthService/index.js +0 -0
  119. package/{services → dist/services}/auth/client/AuthorizationManagementControllerClient/index.d.ts +0 -0
  120. package/{services → dist/services}/auth/client/AuthorizationManagementControllerClient/index.js +0 -0
  121. package/{services → dist/services}/auth/client/index.d.ts +0 -0
  122. package/{services → dist/services}/auth/client/index.js +0 -0
  123. package/{services → dist/services}/auth/index.d.ts +0 -0
  124. package/{services → dist/services}/auth/index.js +0 -0
  125. package/{services → dist/services}/index.d.ts +0 -0
  126. package/{services → dist/services}/index.js +0 -0
  127. package/{utils → dist/utils}/index.d.ts +0 -0
  128. package/{utils → dist/utils}/index.js +0 -0
  129. package/jest.config.js +29 -0
  130. package/package.json +24 -24
  131. package/src/adapters/CollectionViewAdapter/index.ts +390 -0
  132. package/src/adapters/index.ts +1 -0
  133. package/src/data/api/dto/PropertyPathDto.ts +4 -0
  134. package/src/data/api/dto/ReadOptionsDto.ts +8 -0
  135. package/src/data/api/dto/ReadResultDto.ts +13 -0
  136. package/src/data/api/dto/ReadResultMetadataDto.ts +8 -0
  137. package/src/data/api/dto/crud/CrudMetadataDto.ts +4 -0
  138. package/src/data/api/dto/crud/index.ts +1 -0
  139. package/src/data/api/dto/index.ts +4 -0
  140. package/src/data/api/dto/read/ReadMetadataDto.ts +8 -0
  141. package/src/data/api/dto/read/ReadSelectedDefinitionDto.ts +21 -0
  142. package/src/data/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.ts +25 -0
  143. package/src/data/api/dto/read/ReadSelectedNestedCriteriaDto.ts +20 -0
  144. package/src/data/api/dto/read/ReadSelectedOrderingDefinitionDto.ts +8 -0
  145. package/src/data/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.ts +16 -0
  146. package/src/data/api/dto/read/ReadSelectedPaginationDefinitionDto.ts +13 -0
  147. package/src/data/api/dto/read/ReadSelectedSearchDefinitionDto.ts +43 -0
  148. package/src/data/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.ts +186 -0
  149. package/src/data/api/dto/read/index.ts +9 -0
  150. package/src/data/api/dto/response/ApiErrorDto.ts +21 -0
  151. package/src/data/api/dto/response/ApiErrorResponseDto.ts +13 -0
  152. package/src/data/api/dto/response/ApiResponseDto.ts +7 -0
  153. package/src/data/api/dto/response/ApiSuccessResponseDto.ts +13 -0
  154. package/src/data/api/dto/response/MetadataDto.ts +24 -0
  155. package/src/data/api/dto/response/index.ts +5 -0
  156. package/src/data/api/enum/index.ts +2 -0
  157. package/src/data/api/enum/read/ReadSelectedCollectionOperator.ts +17 -0
  158. package/src/data/api/enum/read/ReadSelectedComparisonOperator.ts +96 -0
  159. package/src/data/api/enum/read/ReadSelectedLogicalOperator.ts +16 -0
  160. package/src/data/api/enum/read/ReadSelectedOrderingDirection.ts +13 -0
  161. package/src/data/api/enum/read/ReadSelectedPropertyType.ts +86 -0
  162. package/src/data/api/enum/read/index.ts +5 -0
  163. package/src/data/api/enum/response/ErrorCode.ts +13 -0
  164. package/src/data/api/enum/response/index.ts +1 -0
  165. package/src/data/api/index.ts +3 -0
  166. package/src/data/api/interface/IConcurrencySafe.ts +9 -0
  167. package/src/data/api/interface/IIdentifiable.ts +12 -0
  168. package/src/data/api/interface/IIdentifiableSecondary.ts +9 -0
  169. package/src/data/api/interface/index.ts +3 -0
  170. package/src/data/auth/dto/ClaimDto.ts +4 -0
  171. package/src/data/auth/dto/RegisterRequestDto.ts +4 -0
  172. package/src/data/auth/dto/RoleDto.ts +6 -0
  173. package/src/data/auth/dto/SignInRequestDto.ts +4 -0
  174. package/src/data/auth/dto/TokensDto.ts +4 -0
  175. package/src/data/auth/dto/UserDto.ts +18 -0
  176. package/src/data/auth/dto/UserInfoDto.ts +15 -0
  177. package/src/data/auth/dto/index.ts +4 -0
  178. package/src/data/auth/index.ts +2 -0
  179. package/src/data/auth/policy.ts +63 -0
  180. package/src/data/index.ts +2 -0
  181. package/src/index.ts +4 -0
  182. package/src/services/api/ApiCrudControllerClient/index.ts +129 -0
  183. package/src/services/api/ApiInitializationService/index.ts +254 -0
  184. package/src/services/api/ApiReadControllerClient/index.ts +137 -0
  185. package/src/services/api/HttpService/FetchHttpService.ts +34 -0
  186. package/src/services/api/HttpService/HttpRequestConfig.ts +10 -0
  187. package/src/services/api/HttpService/HttpResponse.ts +14 -0
  188. package/src/services/api/HttpService/IHttpService.ts +17 -0
  189. package/src/services/api/HttpService/README.md +106 -0
  190. package/src/services/api/HttpService/index.ts +12 -0
  191. package/src/services/api/UserManagementControllerClient/index.ts +160 -0
  192. package/src/services/api/index.ts +5 -0
  193. package/src/services/auth/client/AuthService/index.ts +187 -0
  194. package/src/services/auth/client/AuthorizationManagementControllerClient/index.ts +165 -0
  195. package/src/services/auth/client/index.ts +2 -0
  196. package/src/services/auth/index.ts +1 -0
  197. package/src/services/index.ts +2 -0
  198. package/src/utils/authorization/index.ts +47 -0
  199. package/src/utils/index.ts +2 -0
  200. package/src/utils/result/index.ts +25 -0
  201. package/src/utils/search/index.ts +150 -0
  202. package/tests/ApiClients.test.ts +284 -0
  203. package/tests/CollectionViewAdapter.test.ts +392 -0
  204. package/tests/HttpService.test.ts +303 -0
  205. package/tests/setup.ts +76 -0
  206. package/tsconfig.json +19 -0
  207. package/LICENSE.md +0 -7
  208. /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.d.ts +0 -0
  209. /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.js +0 -0
  210. /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCriteriaDto.d.ts +0 -0
  211. /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCriteriaDto.js +0 -0
  212. /package/{data → dist/data}/api/dto/response/ApiErrorResponseDto.d.ts +0 -0
  213. /package/{data → dist/data}/api/dto/response/ApiErrorResponseDto.js +0 -0
  214. /package/{data → dist/data}/api/dto/response/MetadataDto.d.ts +0 -0
  215. /package/{data → dist/data}/api/dto/response/MetadataDto.js +0 -0
  216. /package/{data → dist/data}/api/enum/read/ReadSelectedCollectionOperator.d.ts +0 -0
  217. /package/{data → dist/data}/api/enum/read/ReadSelectedCollectionOperator.js +0 -0
  218. /package/{data → dist/data}/api/enum/response/ErrorCode.d.ts +0 -0
  219. /package/{data → dist/data}/api/enum/response/ErrorCode.js +0 -0
  220. /package/{data → dist/data}/auth/policy.d.ts +0 -0
  221. /package/{data → dist/data}/auth/policy.js +0 -0
  222. /package/{utils → dist/utils}/authorization/index.d.ts +0 -0
  223. /package/{utils → dist/utils}/authorization/index.js +0 -0
  224. /package/{utils → dist/utils}/result/index.d.ts +0 -0
  225. /package/{utils → dist/utils}/result/index.js +0 -0
  226. /package/{utils → dist/utils}/search/index.d.ts +0 -0
  227. /package/{utils → dist/utils}/search/index.js +0 -0
@@ -0,0 +1,392 @@
1
+ const mockReadSelectedAsync = jest.fn<(definition: unknown) => Promise<unknown>>();
2
+
3
+ jest.mock('@services', () => {
4
+ return {
5
+ ApiReadControllerClient: jest.fn().mockImplementation(() => ({
6
+ readSelectedAsync: mockReadSelectedAsync,
7
+ })),
8
+ };
9
+ });
10
+
11
+ jest.mock('@intellegens/cornerstone-client', () => {
12
+ return {
13
+ ReadSelectedOrderingDirection: {
14
+ Ascending: 'Ascending',
15
+ Descending: 'Descending',
16
+ },
17
+ ReadSelectedLogicalOperator: {
18
+ And: 'And',
19
+ Or: 'Or',
20
+ },
21
+ ReadSelectedComparisonOperator: {
22
+ Contains: 'Contains',
23
+ Equal: 'Equal',
24
+ GreaterOrEqual: 'GreaterOrEqual',
25
+ LessOrEqual: 'LessOrEqual',
26
+ },
27
+ ReadSelectedPropertyType: {
28
+ String: 'String',
29
+ Int: 'Int',
30
+ DateTimeOffset: 'DateTimeOffset',
31
+ },
32
+ };
33
+ });
34
+
35
+ import { jest, expect, describe, test, beforeEach, afterEach } from '@jest/globals';
36
+ import { ReadSelectedComparisonOperator, ReadSelectedLogicalOperator, ReadSelectedOrderingDirection, ReadSelectedPropertyType } from '../src/data';
37
+ import { CollectionViewAdapter, CollectionViewAdapterOptions } from '../src/adapters';
38
+ import { dateInterval, searchTerm } from '../src/utils';
39
+
40
+ type TestDto = { id: number; name: string; createdAt: string; rowVersion: string };
41
+
42
+ const makeItems = (n: number): TestDto[] =>
43
+ Array.from({ length: n }, (_, i) => ({
44
+ id: i + 1,
45
+ name: `Item ${i + 1}`,
46
+ createdAt: new Date(2024, 0, i + 1).toISOString(),
47
+ rowVersion: '1',
48
+ }));
49
+
50
+ const fetchData = async () => {
51
+ // _fetchCurrentPageData() in CollectionViewAdapter has 50ms timeout set
52
+ jest.advanceTimersByTime(60);
53
+ // allow any pending microtasks to resolve
54
+ await Promise.resolve();
55
+ };
56
+
57
+ describe('CollectionViewAdapter', () => {
58
+ beforeEach(() => {
59
+ jest.useFakeTimers();
60
+ mockReadSelectedAsync.mockReset();
61
+ });
62
+
63
+ afterEach(() => {
64
+ jest.useRealTimers();
65
+ });
66
+
67
+ // overrides?: Partial for passing options properties from test:
68
+ // const { adapter, callback } = setup({ useTotalItemCount: true });
69
+ const setup = (overrides?: Partial<CollectionViewAdapterOptions<TestDto>>) => {
70
+ const options: CollectionViewAdapterOptions<TestDto> = {
71
+ pagination: {
72
+ useTotalItemCount: true,
73
+ pageSize: 10,
74
+ pageNumber: 1,
75
+ ...overrides?.pagination,
76
+ },
77
+ ordering: {
78
+ maxActiveOrderingColumns: 1,
79
+ orderByPaths: [],
80
+ ...overrides?.ordering,
81
+ },
82
+ search: {
83
+ textSearchableProperties: ['name'],
84
+ numericSearchableProperties: ['id'],
85
+ ...overrides?.search,
86
+ },
87
+ };
88
+
89
+ const callback = jest.fn();
90
+ const adapter = new CollectionViewAdapter<number, TestDto>('ControllerName', callback, options);
91
+
92
+ return { adapter, callback };
93
+ };
94
+
95
+ test('callback returns correct loading status and collection data', async () => {
96
+ const data = makeItems(10);
97
+ mockReadSelectedAsync.mockResolvedValueOnce({ result: data, metadata: { totalCount: 123 } });
98
+
99
+ const { adapter, callback } = setup({ pagination: { useTotalItemCount: true } });
100
+
101
+ await fetchData();
102
+
103
+ expect(callback).toHaveBeenNthCalledWith(1, true, []);
104
+ expect(callback).toHaveBeenLastCalledWith(false, data);
105
+ expect(adapter.totalItemCount).toBe(123);
106
+ });
107
+
108
+ test('adapter options set correct ordering', async () => {
109
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
110
+
111
+ const { adapter } = setup();
112
+ const promise = adapter.setOrdering(['name'], ReadSelectedOrderingDirection.Descending);
113
+
114
+ await fetchData();
115
+ await promise;
116
+
117
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
118
+ expect.objectContaining({
119
+ orderingDefinition: {
120
+ order: [
121
+ {
122
+ propertyPath: ['Name'], // PascalCased
123
+ direction: ReadSelectedOrderingDirection.Descending,
124
+ },
125
+ ],
126
+ },
127
+ }),
128
+ undefined, // The abort signal parameter
129
+ );
130
+
131
+ expect(adapter.getCurrentOrdering()).toEqual({
132
+ orderByPath: ['name'],
133
+ orderDirection: ReadSelectedOrderingDirection.Descending,
134
+ });
135
+ });
136
+
137
+ test('text search is applied to adapter', async () => {
138
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(1), metadata: {} });
139
+
140
+ const { adapter } = setup({ search: { textSearchableProperties: ['name'] } });
141
+
142
+ const searchDefinition = searchTerm('Waldo', ['name'], ['id']);
143
+ if (!searchDefinition) throw new Error('searchDefinition is undefined');
144
+ const promise = adapter.setSearchDefinition(searchDefinition);
145
+ await fetchData();
146
+ await promise;
147
+
148
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
149
+ expect.objectContaining({
150
+ searchDefinition: expect.objectContaining({
151
+ logicalOperator: expect.any(Number),
152
+ propertyCriteria: expect.arrayContaining([
153
+ expect.objectContaining({
154
+ propertyPath: 'Name',
155
+ comparisonOperator: ReadSelectedComparisonOperator.Contains,
156
+ valueType: ReadSelectedPropertyType.String,
157
+ value: 'Waldo',
158
+ }),
159
+ ]),
160
+ }),
161
+ }),
162
+ undefined, // The abort signal parameter
163
+ );
164
+ });
165
+
166
+ test('numeric search is applied to adapter', async () => {
167
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(1), metadata: {} });
168
+
169
+ const { adapter } = setup({ search: { textSearchableProperties: ['id'] } });
170
+
171
+ const searchDefinition = searchTerm('42', ['name'], ['id']);
172
+ if (!searchDefinition) throw new Error('searchDefinition is undefined');
173
+ const promise = adapter.setSearchDefinition(searchDefinition);
174
+ await fetchData();
175
+ await promise;
176
+
177
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
178
+ expect.objectContaining({
179
+ searchDefinition: expect.objectContaining({
180
+ logicalOperator: expect.any(Number),
181
+ propertyCriteria: expect.arrayContaining([
182
+ expect.objectContaining({
183
+ propertyPath: ['Id'],
184
+ comparisonOperator: ReadSelectedComparisonOperator.Equal,
185
+ valueType: ReadSelectedPropertyType.Int,
186
+ value: 42,
187
+ }),
188
+ ]),
189
+ }),
190
+ }),
191
+ undefined,
192
+ );
193
+ });
194
+
195
+ test('applies date range filter from-to on the same date type property', async () => {
196
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(1), metadata: {} });
197
+
198
+ const { adapter } = setup();
199
+ const from = new Date('2024-01-05T00:00:00.000Z');
200
+ const to = new Date('2024-01-20T00:00:00.000Z');
201
+
202
+ const searchDefinition = dateInterval<TestDto>(
203
+ from,
204
+ to,
205
+ ReadSelectedComparisonOperator.GreaterOrEqual,
206
+ ReadSelectedComparisonOperator.LessOrEqual,
207
+ ReadSelectedPropertyType.DateTimeOffset,
208
+ 'createdAt',
209
+ );
210
+ if (!searchDefinition) throw new Error('searchDefinition is undefined');
211
+ const promise = adapter.setSearchDefinition(searchDefinition);
212
+ await fetchData();
213
+ await promise;
214
+
215
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
216
+ expect.objectContaining({
217
+ searchDefinition: expect.objectContaining({
218
+ logicalOperator: ReadSelectedLogicalOperator.And,
219
+ propertyCriteria: expect.arrayContaining([
220
+ expect.objectContaining({
221
+ propertyPath: ['DateProperty'],
222
+ comparisonOperator: ReadSelectedComparisonOperator.GreaterOrEqual,
223
+ valueType: ReadSelectedPropertyType.DateTimeOffset,
224
+ value: expect.any(Object),
225
+ }),
226
+ expect.objectContaining({
227
+ propertyPath: ['DateProperty'],
228
+ comparisonOperator: ReadSelectedComparisonOperator.LessOrEqual,
229
+ valueType: ReadSelectedPropertyType.DateTimeOffset,
230
+ value: expect.any(Object),
231
+ }),
232
+ ]),
233
+ }),
234
+ }),
235
+ undefined, // The abort signal parameter
236
+ );
237
+ });
238
+
239
+ test('applies pagination: page size and jump to page set skip/limit', async () => {
240
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(5), metadata: {} });
241
+
242
+ const { adapter } = setup({ pagination: { useTotalItemCount: true, pageSize: 5, pageNumber: 1 } });
243
+
244
+ const promise = adapter.jumpToPage(3);
245
+ await fetchData();
246
+ await promise;
247
+
248
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
249
+ // ReadSelectedDefinitionDto
250
+ expect.objectContaining({
251
+ paginationDefinition: { skip: 10, limit: 5 },
252
+ searchDefinition: undefined,
253
+ }),
254
+ undefined, // The abort signal parameter
255
+ );
256
+ });
257
+
258
+ test('calculates total when useTotalItemCount=false and last page is partial', async () => {
259
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
260
+
261
+ const { adapter } = setup({ pagination: { useTotalItemCount: false, pageSize: 5, pageNumber: 2 } });
262
+
263
+ await fetchData();
264
+
265
+ expect(adapter.totalItemCount).toBe(8); // 3 items on page 2 (5 skipped on first page) ... 5+3=8
266
+ });
267
+
268
+ test('setOrdering correctly handles PropertyPathDto array comparison and limits columns', async () => {
269
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
270
+
271
+ const { adapter } = setup({ ordering: { maxActiveOrderingColumns: 2, orderByPaths: [] } });
272
+
273
+ // Add first ordering
274
+ let promise = adapter.setOrdering(['name'], ReadSelectedOrderingDirection.Descending);
275
+ await fetchData();
276
+ await promise;
277
+
278
+ expect(adapter.getCurrentOrdering()).toEqual({
279
+ orderByPath: ['name'],
280
+ orderDirection: ReadSelectedOrderingDirection.Descending,
281
+ });
282
+
283
+ // Add second ordering
284
+ promise = adapter.setOrdering(['id'], ReadSelectedOrderingDirection.Ascending);
285
+ await fetchData();
286
+ await promise;
287
+
288
+ // Should have both orderings, with 'id' first (most recent)
289
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
290
+ expect.objectContaining({
291
+ orderingDefinition: {
292
+ order: [
293
+ {
294
+ propertyPath: ['Id'], // PascalCased
295
+ direction: ReadSelectedOrderingDirection.Ascending,
296
+ },
297
+ {
298
+ propertyPath: ['Name'], // PascalCased
299
+ direction: ReadSelectedOrderingDirection.Descending,
300
+ },
301
+ ],
302
+ },
303
+ }),
304
+ undefined,
305
+ );
306
+
307
+ // Add third ordering - should remove the oldest one (name)
308
+ promise = adapter.setOrdering(['createdAt'], ReadSelectedOrderingDirection.Descending);
309
+ await fetchData();
310
+ await promise;
311
+
312
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
313
+ expect.objectContaining({
314
+ orderingDefinition: {
315
+ order: [
316
+ {
317
+ propertyPath: ['CreatedAt'], // PascalCased
318
+ direction: ReadSelectedOrderingDirection.Descending,
319
+ },
320
+ {
321
+ propertyPath: ['Id'], // PascalCased
322
+ direction: ReadSelectedOrderingDirection.Ascending,
323
+ },
324
+ ],
325
+ },
326
+ }),
327
+ undefined,
328
+ );
329
+
330
+ // Update existing ordering - should replace, not add
331
+ promise = adapter.setOrdering(['id'], ReadSelectedOrderingDirection.Descending);
332
+ await fetchData();
333
+ await promise;
334
+
335
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
336
+ expect.objectContaining({
337
+ orderingDefinition: {
338
+ order: [
339
+ {
340
+ propertyPath: ['Id'], // PascalCased - updated direction
341
+ direction: ReadSelectedOrderingDirection.Descending,
342
+ },
343
+ {
344
+ propertyPath: ['CreatedAt'], // PascalCased
345
+ direction: ReadSelectedOrderingDirection.Descending,
346
+ },
347
+ ],
348
+ },
349
+ }),
350
+ undefined,
351
+ );
352
+ });
353
+
354
+ test('setOrdering correctly compares complex PropertyPathDto arrays', async () => {
355
+ mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
356
+
357
+ const { adapter } = setup({ ordering: { maxActiveOrderingColumns: 3, orderByPaths: [] } });
358
+
359
+ // Add ordering with nested property path
360
+ let promise = adapter.setOrdering(['user', 'profile', 'name'], ReadSelectedOrderingDirection.Ascending);
361
+ await fetchData();
362
+ await promise;
363
+
364
+ // Add different nested path
365
+ promise = adapter.setOrdering(['user', 'settings', 'theme'], ReadSelectedOrderingDirection.Descending);
366
+ await fetchData();
367
+ await promise;
368
+
369
+ // Update the first nested path - should replace it, not add new
370
+ promise = adapter.setOrdering(['user', 'profile', 'name'], ReadSelectedOrderingDirection.Descending);
371
+ await fetchData();
372
+ await promise;
373
+
374
+ expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
375
+ expect.objectContaining({
376
+ orderingDefinition: {
377
+ order: [
378
+ {
379
+ propertyPath: ['User', 'Profile', 'Name'], // PascalCased - updated direction
380
+ direction: ReadSelectedOrderingDirection.Descending,
381
+ },
382
+ {
383
+ propertyPath: ['User', 'Settings', 'Theme'], // PascalCased
384
+ direction: ReadSelectedOrderingDirection.Descending,
385
+ },
386
+ ],
387
+ },
388
+ }),
389
+ undefined,
390
+ );
391
+ });
392
+ });
@@ -0,0 +1,303 @@
1
+ import { AngularHttpService } from '../../angular/projects/intellegens/cornerstone-client-angular/src/lib/services/AngularHttpService';
2
+ import { FetchHttpService, IHttpService } from '../src';
3
+ import { jest } from '@jest/globals';
4
+ import { of, throwError } from 'rxjs';
5
+
6
+ // Mock fetch for testing
7
+ const mockFetch = jest.fn() as jest.MockedFunction<typeof fetch>;
8
+ global.fetch = mockFetch;
9
+
10
+ // Mock Angular HttpClient
11
+ const mockAngularHttpClient = {
12
+ get: jest.fn(),
13
+ post: jest.fn(),
14
+ put: jest.fn(),
15
+ delete: jest.fn(),
16
+ patch: jest.fn(),
17
+ };
18
+
19
+ describe('HttpService Implementations', () => {
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ describe('FetchHttpService', () => {
25
+ let fetchService: FetchHttpService;
26
+
27
+ beforeEach(() => {
28
+ fetchService = new FetchHttpService();
29
+ });
30
+
31
+ it('should make a GET request successfully', async () => {
32
+ const mockResponse = {
33
+ ok: true,
34
+ status: 200,
35
+ statusText: 'OK',
36
+ headers: new Map([['content-type', 'application/json']]),
37
+ json: jest.fn<() => Promise<any>>().mockResolvedValue({ data: 'test' }),
38
+ text: jest.fn<() => Promise<string>>().mockResolvedValue('{"data":"test"}'),
39
+ };
40
+
41
+ // Mock Headers.forEach
42
+ mockResponse.headers.forEach = jest.fn((callback: (value: string, key: string) => void) => {
43
+ callback('application/json', 'content-type');
44
+ });
45
+
46
+ mockFetch.mockResolvedValue(mockResponse as any);
47
+
48
+ const result = await fetchService.request('https://api.example.com/test');
49
+
50
+ expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/test', {
51
+ method: 'GET',
52
+ headers: undefined,
53
+ body: undefined,
54
+ credentials: 'include',
55
+ });
56
+
57
+ expect(result.ok).toBe(true);
58
+ expect(result.status).toBe(200);
59
+ expect(result.statusText).toBe('OK');
60
+ expect(result.headers).toEqual({ 'content-type': 'application/json' });
61
+
62
+ const jsonData = await result.json();
63
+ expect(jsonData).toEqual({ data: 'test' });
64
+ });
65
+
66
+ it('should make a POST request with body', async () => {
67
+ const mockResponse = {
68
+ ok: true,
69
+ status: 201,
70
+ statusText: 'Created',
71
+ headers: new Map(),
72
+ json: jest.fn<() => Promise<any>>().mockResolvedValue({ id: 1 }),
73
+ text: jest.fn<() => Promise<string>>().mockResolvedValue('{"id":1}'),
74
+ };
75
+
76
+ mockResponse.headers.forEach = jest.fn();
77
+ mockFetch.mockResolvedValue(mockResponse as any);
78
+
79
+ const testData = { name: 'test' };
80
+ const result = await fetchService.request('https://api.example.com/create', {
81
+ method: 'POST',
82
+ headers: { 'Content-Type': 'application/json' },
83
+ body: JSON.stringify(testData),
84
+ credentials: 'include',
85
+ });
86
+
87
+ expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/create', {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: JSON.stringify(testData),
91
+ credentials: 'include',
92
+ });
93
+
94
+ expect(result.ok).toBe(true);
95
+ expect(result.status).toBe(201);
96
+ });
97
+
98
+ it('should handle fetch errors', async () => {
99
+ mockFetch.mockRejectedValue(new Error('Network error'));
100
+
101
+ try {
102
+ await fetchService.request('https://api.example.com/error');
103
+ fail('Should have thrown an error');
104
+ } catch (error: any) {
105
+ expect(error.message).toBe('Network error');
106
+ }
107
+ });
108
+
109
+ it('should handle non-ok responses', async () => {
110
+ const mockResponse = {
111
+ ok: false,
112
+ status: 404,
113
+ statusText: 'Not Found',
114
+ headers: new Map(),
115
+ json: jest.fn<() => Promise<any>>().mockResolvedValue({ error: 'Not found' }),
116
+ text: jest.fn<() => Promise<string>>().mockResolvedValue('{"error":"Not found"}'),
117
+ };
118
+
119
+ mockResponse.headers.forEach = jest.fn();
120
+ mockFetch.mockResolvedValue(mockResponse as any);
121
+
122
+ const result = await fetchService.request('https://api.example.com/notfound');
123
+
124
+ expect(result.ok).toBe(false);
125
+ expect(result.status).toBe(404);
126
+ expect(result.statusText).toBe('Not Found');
127
+ });
128
+ });
129
+
130
+ describe('AngularHttpService', () => {
131
+ let angularService: AngularHttpService;
132
+
133
+ beforeEach(() => {
134
+ angularService = new AngularHttpService(mockAngularHttpClient as any);
135
+ });
136
+
137
+ it('should throw error if no HttpClient provided', () => {
138
+ expect(() => new AngularHttpService(null as any)).toThrow('Angular HttpClient instance is required');
139
+ });
140
+
141
+ it('should make a GET request successfully', async () => {
142
+ const mockResponse = {
143
+ status: 200,
144
+ statusText: 'OK',
145
+ body: { data: 'test' },
146
+ headers: {
147
+ keys: jest.fn().mockReturnValue(['content-type']),
148
+ get: jest.fn().mockReturnValue('application/json'),
149
+ },
150
+ };
151
+
152
+ mockAngularHttpClient.get.mockReturnValue(of(mockResponse));
153
+
154
+ const result = await angularService.request('https://api.example.com/test');
155
+
156
+ expect(mockAngularHttpClient.get).toHaveBeenCalledWith('https://api.example.com/test', {
157
+ headers: {},
158
+ withCredentials: false,
159
+ observe: 'response',
160
+ responseType: 'json',
161
+ });
162
+
163
+ expect(result.ok).toBe(true);
164
+ expect(result.status).toBe(200);
165
+ expect(result.statusText).toBe('OK');
166
+ expect(result.headers).toEqual({ 'content-type': 'application/json' });
167
+
168
+ const jsonData = await result.json();
169
+ expect(jsonData).toEqual({ data: 'test' });
170
+ });
171
+
172
+ it('should make a POST request with body', async () => {
173
+ const mockResponse = {
174
+ status: 201,
175
+ statusText: 'Created',
176
+ body: { id: 1 },
177
+ headers: {
178
+ keys: jest.fn().mockReturnValue([]),
179
+ get: jest.fn(),
180
+ },
181
+ };
182
+
183
+ mockAngularHttpClient.post.mockReturnValue(of(mockResponse));
184
+
185
+ const testData = { name: 'test' };
186
+ const result = await angularService.request('https://api.example.com/create', {
187
+ method: 'POST',
188
+ headers: { 'Content-Type': 'application/json' },
189
+ body: JSON.stringify(testData),
190
+ credentials: 'include',
191
+ });
192
+
193
+ expect(mockAngularHttpClient.post).toHaveBeenCalledWith('https://api.example.com/create', testData, {
194
+ headers: { 'Content-Type': 'application/json' },
195
+ withCredentials: true,
196
+ observe: 'response',
197
+ responseType: 'json',
198
+ });
199
+
200
+ expect(result.ok).toBe(true);
201
+ expect(result.status).toBe(201);
202
+ });
203
+
204
+ it('should handle PUT requests', async () => {
205
+ const mockResponse = {
206
+ status: 200,
207
+ statusText: 'OK',
208
+ body: { updated: true },
209
+ headers: {
210
+ keys: jest.fn().mockReturnValue([]),
211
+ get: jest.fn(),
212
+ },
213
+ };
214
+
215
+ mockAngularHttpClient.put.mockReturnValue(of(mockResponse));
216
+
217
+ const result = await angularService.request('https://api.example.com/update', {
218
+ method: 'PUT',
219
+ body: JSON.stringify({ id: 1, name: 'updated' }),
220
+ });
221
+
222
+ expect(mockAngularHttpClient.put).toHaveBeenCalled();
223
+ expect(result.ok).toBe(true);
224
+ expect(result.status).toBe(200);
225
+ });
226
+
227
+ it('should handle DELETE requests', async () => {
228
+ const mockResponse = {
229
+ status: 204,
230
+ statusText: 'No Content',
231
+ body: null,
232
+ headers: {
233
+ keys: jest.fn().mockReturnValue([]),
234
+ get: jest.fn(),
235
+ },
236
+ };
237
+
238
+ mockAngularHttpClient.delete.mockReturnValue(of(mockResponse));
239
+
240
+ const result = await angularService.request('https://api.example.com/delete/1', {
241
+ method: 'DELETE',
242
+ });
243
+
244
+ expect(mockAngularHttpClient.delete).toHaveBeenCalled();
245
+ expect(result.ok).toBe(true);
246
+ expect(result.status).toBe(204);
247
+ });
248
+
249
+ it('should handle Angular HttpErrorResponse', async () => {
250
+ const mockError = {
251
+ status: 400,
252
+ statusText: 'Bad Request',
253
+ error: { message: 'Invalid data' },
254
+ headers: {
255
+ keys: jest.fn().mockReturnValue(['content-type']),
256
+ get: jest.fn().mockReturnValue('application/json'),
257
+ },
258
+ };
259
+
260
+ mockAngularHttpClient.get.mockReturnValue(throwError(() => mockError));
261
+
262
+ const result = await angularService.request('https://api.example.com/error');
263
+
264
+ expect(result.ok).toBe(false);
265
+ expect(result.status).toBe(400);
266
+ expect(result.statusText).toBe('Bad Request');
267
+ expect(result.headers).toEqual({ 'content-type': 'application/json' });
268
+
269
+ const errorData = await result.json();
270
+ expect(errorData).toEqual({ message: 'Invalid data' });
271
+ });
272
+
273
+ it('should throw error for unsupported HTTP methods', async () => {
274
+ try {
275
+ await angularService.request('https://api.example.com/test', { method: 'OPTIONS' as any });
276
+ fail('Should have thrown an error');
277
+ } catch (error: any) {
278
+ expect(error.message).toContain('Unsupported HTTP method: OPTIONS');
279
+ }
280
+ });
281
+
282
+ it('should handle non-Angular errors', async () => {
283
+ mockAngularHttpClient.get.mockReturnValue(throwError(() => new Error('Network error')));
284
+
285
+ try {
286
+ await angularService.request('https://api.example.com/error');
287
+ fail('Should have thrown an error');
288
+ } catch (error: any) {
289
+ expect(error.message).toBe('Network error');
290
+ }
291
+ });
292
+ });
293
+
294
+ describe('Interface Compliance', () => {
295
+ it('should implement IHttpService interface correctly', () => {
296
+ const fetchService: IHttpService = new FetchHttpService();
297
+ const angularService: IHttpService = new AngularHttpService(mockAngularHttpClient as any);
298
+
299
+ expect(typeof fetchService.request).toBe('function');
300
+ expect(typeof angularService.request).toBe('function');
301
+ });
302
+ });
303
+ });