json-rest-api 1.0.13 → 1.0.15
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.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +13 -0
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +9 -5
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { cleanTables } from './helpers/test-utils.js'
|
|
5
|
-
import { createPositioningApi } from './fixtures/api-configs.js'
|
|
6
|
-
import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js'
|
|
1
|
+
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import knexLib from 'knex'
|
|
4
|
+
import { cleanTables } from './helpers/test-utils.js'
|
|
5
|
+
import { createPositioningApi } from './fixtures/api-configs.js'
|
|
6
|
+
import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js'
|
|
7
7
|
|
|
8
8
|
// Create Knex instance for tests
|
|
9
9
|
const knex = knexLib({
|
|
@@ -12,43 +12,43 @@ const knex = knexLib({
|
|
|
12
12
|
filename: ':memory:'
|
|
13
13
|
},
|
|
14
14
|
useNullAsDefault: true
|
|
15
|
-
})
|
|
15
|
+
})
|
|
16
16
|
|
|
17
17
|
describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
18
|
-
let api
|
|
18
|
+
let api
|
|
19
19
|
|
|
20
20
|
before(async () => {
|
|
21
21
|
// Initialize API
|
|
22
|
-
api = await createPositioningApi(knex)
|
|
23
|
-
|
|
22
|
+
api = await createPositioningApi(knex)
|
|
23
|
+
|
|
24
24
|
// Install positioning plugin with default configuration
|
|
25
25
|
await api.use(PositioningPlugin, {
|
|
26
26
|
field: 'position',
|
|
27
27
|
filters: ['project', 'status'], // Default filters for most tests
|
|
28
28
|
defaultPosition: 'last'
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
32
|
after(async () => {
|
|
33
|
-
await knex.destroy()
|
|
34
|
-
})
|
|
35
|
-
|
|
33
|
+
await knex.destroy()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
36
|
beforeEach(async () => {
|
|
37
37
|
await cleanTables(knex, [
|
|
38
38
|
'positioning_categories',
|
|
39
39
|
'positioning_tasks',
|
|
40
40
|
'positioning_projects',
|
|
41
41
|
'positioning_items'
|
|
42
|
-
])
|
|
43
|
-
})
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
44
|
|
|
45
45
|
describe('Multiple Filter Fields', () => {
|
|
46
46
|
it('should maintain separate position sequences for each filter combination', async () => {
|
|
47
47
|
// Plugin already installed with filters in before hook
|
|
48
48
|
|
|
49
49
|
// Create projects
|
|
50
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
51
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
50
|
+
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
51
|
+
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
52
52
|
|
|
53
53
|
// Create items in different filter combinations
|
|
54
54
|
// Project 1, Active
|
|
@@ -56,46 +56,46 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
56
56
|
name: 'P1 Active 1',
|
|
57
57
|
project: project1.id,
|
|
58
58
|
status: 'active'
|
|
59
|
-
})
|
|
59
|
+
})
|
|
60
60
|
|
|
61
61
|
const p1Active2 = await api.resources.items.post({
|
|
62
62
|
name: 'P1 Active 2',
|
|
63
63
|
project: project1.id,
|
|
64
64
|
status: 'active'
|
|
65
|
-
})
|
|
65
|
+
})
|
|
66
66
|
|
|
67
67
|
// Project 1, Archived
|
|
68
68
|
const p1Archived1 = await api.resources.items.post({
|
|
69
69
|
name: 'P1 Archived 1',
|
|
70
70
|
project: project1.id,
|
|
71
71
|
status: 'archived'
|
|
72
|
-
})
|
|
72
|
+
})
|
|
73
73
|
|
|
74
74
|
const p1Archived2 = await api.resources.items.post({
|
|
75
75
|
name: 'P1 Archived 2',
|
|
76
76
|
project: project1.id,
|
|
77
77
|
status: 'archived'
|
|
78
|
-
})
|
|
78
|
+
})
|
|
79
79
|
|
|
80
80
|
// Project 2, Active
|
|
81
81
|
const p2Active1 = await api.resources.items.post({
|
|
82
82
|
name: 'P2 Active 1',
|
|
83
83
|
project: project2.id,
|
|
84
84
|
status: 'active'
|
|
85
|
-
})
|
|
85
|
+
})
|
|
86
86
|
|
|
87
87
|
const p2Active2 = await api.resources.items.post({
|
|
88
88
|
name: 'P2 Active 2',
|
|
89
89
|
project: project2.id,
|
|
90
90
|
status: 'active'
|
|
91
|
-
})
|
|
91
|
+
})
|
|
92
92
|
|
|
93
93
|
// Project 2, Archived
|
|
94
94
|
const p2Archived1 = await api.resources.items.post({
|
|
95
95
|
name: 'P2 Archived 1',
|
|
96
96
|
project: project2.id,
|
|
97
97
|
status: 'archived'
|
|
98
|
-
})
|
|
98
|
+
})
|
|
99
99
|
|
|
100
100
|
// Query each combination
|
|
101
101
|
const { data: p1ActiveItems } = await api.resources.items.query({
|
|
@@ -103,71 +103,71 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
103
103
|
filters: { project: project1.id, status: 'active' },
|
|
104
104
|
sort: ['position']
|
|
105
105
|
}
|
|
106
|
-
})
|
|
106
|
+
})
|
|
107
107
|
|
|
108
|
-
assert.equal(p1ActiveItems.length, 2)
|
|
109
|
-
assert.equal(p1ActiveItems[0].name, 'P1 Active 1')
|
|
110
|
-
assert.equal(p1ActiveItems[1].name, 'P1 Active 2')
|
|
108
|
+
assert.equal(p1ActiveItems.length, 2)
|
|
109
|
+
assert.equal(p1ActiveItems[0].name, 'P1 Active 1')
|
|
110
|
+
assert.equal(p1ActiveItems[1].name, 'P1 Active 2')
|
|
111
111
|
|
|
112
112
|
const { data: p1ArchivedItems } = await api.resources.items.query({
|
|
113
113
|
queryParams: {
|
|
114
114
|
filters: { project: project1.id, status: 'archived' },
|
|
115
115
|
sort: ['position']
|
|
116
116
|
}
|
|
117
|
-
})
|
|
117
|
+
})
|
|
118
118
|
|
|
119
|
-
assert.equal(p1ArchivedItems.length, 2)
|
|
120
|
-
assert.equal(p1ArchivedItems[0].name, 'P1 Archived 1')
|
|
121
|
-
assert.equal(p1ArchivedItems[1].name, 'P1 Archived 2')
|
|
119
|
+
assert.equal(p1ArchivedItems.length, 2)
|
|
120
|
+
assert.equal(p1ArchivedItems[0].name, 'P1 Archived 1')
|
|
121
|
+
assert.equal(p1ArchivedItems[1].name, 'P1 Archived 2')
|
|
122
122
|
|
|
123
123
|
const { data: p2ActiveItems } = await api.resources.items.query({
|
|
124
124
|
queryParams: {
|
|
125
125
|
filters: { project: project2.id, status: 'active' },
|
|
126
126
|
sort: ['position']
|
|
127
127
|
}
|
|
128
|
-
})
|
|
128
|
+
})
|
|
129
129
|
|
|
130
|
-
assert.equal(p2ActiveItems.length, 2)
|
|
131
|
-
assert.equal(p2ActiveItems[0].name, 'P2 Active 1')
|
|
132
|
-
assert.equal(p2ActiveItems[1].name, 'P2 Active 2')
|
|
130
|
+
assert.equal(p2ActiveItems.length, 2)
|
|
131
|
+
assert.equal(p2ActiveItems[0].name, 'P2 Active 1')
|
|
132
|
+
assert.equal(p2ActiveItems[1].name, 'P2 Active 2')
|
|
133
133
|
|
|
134
134
|
// Verify positions are independent across filter combinations
|
|
135
135
|
// Items in different groups can have the same position
|
|
136
|
-
assert.equal(p1Active1.position, 'a0')
|
|
137
|
-
assert.equal(p1Archived1.position, 'a0')
|
|
138
|
-
assert.equal(p2Active1.position, 'a0')
|
|
139
|
-
assert.equal(p2Archived1.position, 'a0')
|
|
140
|
-
})
|
|
136
|
+
assert.equal(p1Active1.position, 'a0')
|
|
137
|
+
assert.equal(p1Archived1.position, 'a0')
|
|
138
|
+
assert.equal(p2Active1.position, 'a0')
|
|
139
|
+
assert.equal(p2Archived1.position, 'a0')
|
|
140
|
+
})
|
|
141
141
|
|
|
142
142
|
it('should handle positioning with null values in multi-filter setup', async () => {
|
|
143
143
|
// Plugin already installed with filters in before hook
|
|
144
144
|
|
|
145
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
145
|
+
const project = await api.resources.projects.post({ name: 'Project' })
|
|
146
146
|
|
|
147
147
|
// Create items with various null combinations
|
|
148
148
|
const item1 = await api.resources.items.post({
|
|
149
149
|
name: 'Item 1',
|
|
150
150
|
project: project.id,
|
|
151
151
|
status: 'active'
|
|
152
|
-
})
|
|
152
|
+
})
|
|
153
153
|
|
|
154
154
|
const item2 = await api.resources.items.post({
|
|
155
155
|
name: 'Item 2',
|
|
156
156
|
project: project.id,
|
|
157
157
|
status: null // Null status
|
|
158
|
-
})
|
|
158
|
+
})
|
|
159
159
|
|
|
160
160
|
const item3 = await api.resources.items.post({
|
|
161
161
|
name: 'Item 3',
|
|
162
162
|
project: null, // Null project
|
|
163
163
|
status: 'active'
|
|
164
|
-
})
|
|
164
|
+
})
|
|
165
165
|
|
|
166
166
|
const item4 = await api.resources.items.post({
|
|
167
167
|
name: 'Item 4',
|
|
168
168
|
project: null,
|
|
169
169
|
status: null // Both null
|
|
170
|
-
})
|
|
170
|
+
})
|
|
171
171
|
|
|
172
172
|
// Query different combinations
|
|
173
173
|
const { data: projectActiveItems } = await api.resources.items.query({
|
|
@@ -175,61 +175,61 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
175
175
|
filters: { project: project.id, status: 'active' },
|
|
176
176
|
sort: ['position']
|
|
177
177
|
}
|
|
178
|
-
})
|
|
179
|
-
assert.equal(projectActiveItems.length, 1)
|
|
180
|
-
assert.equal(projectActiveItems[0].name, 'Item 1')
|
|
178
|
+
})
|
|
179
|
+
assert.equal(projectActiveItems.length, 1)
|
|
180
|
+
assert.equal(projectActiveItems[0].name, 'Item 1')
|
|
181
181
|
|
|
182
182
|
const { data: projectNullStatusItems } = await api.resources.items.query({
|
|
183
183
|
queryParams: {
|
|
184
184
|
filters: { project: project.id, status: null },
|
|
185
185
|
sort: ['position']
|
|
186
186
|
}
|
|
187
|
-
})
|
|
188
|
-
assert.equal(projectNullStatusItems.length, 1)
|
|
189
|
-
assert.equal(projectNullStatusItems[0].name, 'Item 2')
|
|
187
|
+
})
|
|
188
|
+
assert.equal(projectNullStatusItems.length, 1)
|
|
189
|
+
assert.equal(projectNullStatusItems[0].name, 'Item 2')
|
|
190
190
|
|
|
191
191
|
const { data: nullProjectActiveItems } = await api.resources.items.query({
|
|
192
192
|
queryParams: {
|
|
193
193
|
filters: { project: null, status: 'active' },
|
|
194
194
|
sort: ['position']
|
|
195
195
|
}
|
|
196
|
-
})
|
|
197
|
-
assert.equal(nullProjectActiveItems.length, 1)
|
|
198
|
-
assert.equal(nullProjectActiveItems[0].name, 'Item 3')
|
|
196
|
+
})
|
|
197
|
+
assert.equal(nullProjectActiveItems.length, 1)
|
|
198
|
+
assert.equal(nullProjectActiveItems[0].name, 'Item 3')
|
|
199
199
|
|
|
200
200
|
const { data: bothNullItems } = await api.resources.items.query({
|
|
201
201
|
queryParams: {
|
|
202
202
|
filters: { project: null, status: null },
|
|
203
203
|
sort: ['position']
|
|
204
204
|
}
|
|
205
|
-
})
|
|
206
|
-
assert.equal(bothNullItems.length, 1)
|
|
207
|
-
assert.equal(bothNullItems[0].name, 'Item 4')
|
|
208
|
-
})
|
|
205
|
+
})
|
|
206
|
+
assert.equal(bothNullItems.length, 1)
|
|
207
|
+
assert.equal(bothNullItems[0].name, 'Item 4')
|
|
208
|
+
})
|
|
209
209
|
|
|
210
210
|
it('should handle beforeId correctly with multi-filter positioning', async () => {
|
|
211
211
|
// Plugin already installed with filters in before hook
|
|
212
212
|
|
|
213
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
213
|
+
const project = await api.resources.projects.post({ name: 'Project' })
|
|
214
214
|
|
|
215
215
|
// Create initial items
|
|
216
216
|
const item1 = await api.resources.items.post({
|
|
217
217
|
name: 'Item 1',
|
|
218
218
|
project: project.id,
|
|
219
219
|
status: 'active'
|
|
220
|
-
})
|
|
220
|
+
})
|
|
221
221
|
|
|
222
222
|
const item2 = await api.resources.items.post({
|
|
223
223
|
name: 'Item 2',
|
|
224
224
|
project: project.id,
|
|
225
225
|
status: 'active'
|
|
226
|
-
})
|
|
226
|
+
})
|
|
227
227
|
|
|
228
228
|
const item3 = await api.resources.items.post({
|
|
229
229
|
name: 'Item 3',
|
|
230
230
|
project: project.id,
|
|
231
231
|
status: 'active'
|
|
232
|
-
})
|
|
232
|
+
})
|
|
233
233
|
|
|
234
234
|
// Create item in different filter group with beforeId from first group
|
|
235
235
|
const item4 = await api.resources.items.post({
|
|
@@ -237,7 +237,7 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
237
237
|
project: project.id,
|
|
238
238
|
status: 'archived',
|
|
239
239
|
beforeId: item2.id // This should be ignored since it's in a different filter group
|
|
240
|
-
})
|
|
240
|
+
})
|
|
241
241
|
|
|
242
242
|
// Item 4 should be first in its filter group
|
|
243
243
|
const { data: archivedItems } = await api.resources.items.query({
|
|
@@ -245,11 +245,11 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
245
245
|
filters: { project: project.id, status: 'archived' },
|
|
246
246
|
sort: ['position']
|
|
247
247
|
}
|
|
248
|
-
})
|
|
248
|
+
})
|
|
249
249
|
|
|
250
|
-
assert.equal(archivedItems.length, 1)
|
|
251
|
-
assert.equal(archivedItems[0].name, 'Item 4')
|
|
252
|
-
assert.equal(archivedItems[0].position, 'a0')
|
|
250
|
+
assert.equal(archivedItems.length, 1)
|
|
251
|
+
assert.equal(archivedItems[0].name, 'Item 4')
|
|
252
|
+
assert.equal(archivedItems[0].position, 'a0') // First in its group
|
|
253
253
|
|
|
254
254
|
// Now position an item before another in the same filter group
|
|
255
255
|
const item5 = await api.resources.items.post({
|
|
@@ -257,59 +257,59 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
257
257
|
project: project.id,
|
|
258
258
|
status: 'active',
|
|
259
259
|
beforeId: item2.id
|
|
260
|
-
})
|
|
260
|
+
})
|
|
261
261
|
|
|
262
262
|
const { data: activeItems } = await api.resources.items.query({
|
|
263
263
|
queryParams: {
|
|
264
264
|
filters: { project: project.id, status: 'active' },
|
|
265
265
|
sort: ['position']
|
|
266
266
|
}
|
|
267
|
-
})
|
|
267
|
+
})
|
|
268
268
|
|
|
269
|
-
assert.equal(activeItems.length, 4)
|
|
270
|
-
const item5Index = activeItems.findIndex(i => i.name === 'Item 5')
|
|
271
|
-
const item2Index = activeItems.findIndex(i => i.name === 'Item 2')
|
|
272
|
-
assert(item5Index < item2Index, 'Item 5 should be before Item 2')
|
|
273
|
-
})
|
|
269
|
+
assert.equal(activeItems.length, 4)
|
|
270
|
+
const item5Index = activeItems.findIndex(i => i.name === 'Item 5')
|
|
271
|
+
const item2Index = activeItems.findIndex(i => i.name === 'Item 2')
|
|
272
|
+
assert(item5Index < item2Index, 'Item 5 should be before Item 2')
|
|
273
|
+
})
|
|
274
274
|
|
|
275
275
|
it('should handle updates that change filter values', async () => {
|
|
276
276
|
// Plugin already installed with filters in before hook
|
|
277
277
|
|
|
278
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
279
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
278
|
+
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
279
|
+
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
280
280
|
|
|
281
281
|
// Create items in project 1
|
|
282
282
|
const item1 = await api.resources.items.post({
|
|
283
283
|
name: 'Item 1',
|
|
284
284
|
project: project1.id,
|
|
285
285
|
status: 'active'
|
|
286
|
-
})
|
|
286
|
+
})
|
|
287
287
|
|
|
288
288
|
const item2 = await api.resources.items.post({
|
|
289
289
|
name: 'Item 2',
|
|
290
290
|
project: project1.id,
|
|
291
291
|
status: 'active'
|
|
292
|
-
})
|
|
292
|
+
})
|
|
293
293
|
|
|
294
294
|
const item3 = await api.resources.items.post({
|
|
295
295
|
name: 'Item 3',
|
|
296
296
|
project: project1.id,
|
|
297
297
|
status: 'active'
|
|
298
|
-
})
|
|
298
|
+
})
|
|
299
299
|
|
|
300
300
|
// Create items in project 2
|
|
301
301
|
const item4 = await api.resources.items.post({
|
|
302
302
|
name: 'Item 4',
|
|
303
303
|
project: project2.id,
|
|
304
304
|
status: 'active'
|
|
305
|
-
})
|
|
305
|
+
})
|
|
306
306
|
|
|
307
307
|
// Move item2 to project 2, positioning it before item4
|
|
308
308
|
await api.resources.items.patch({
|
|
309
309
|
id: item2.id,
|
|
310
310
|
project: project2.id,
|
|
311
311
|
beforeId: item4.id
|
|
312
|
-
})
|
|
312
|
+
})
|
|
313
313
|
|
|
314
314
|
// Verify project 1 now has 2 items
|
|
315
315
|
const { data: p1Items } = await api.resources.items.query({
|
|
@@ -317,11 +317,11 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
317
317
|
filters: { project: project1.id, status: 'active' },
|
|
318
318
|
sort: ['position']
|
|
319
319
|
}
|
|
320
|
-
})
|
|
320
|
+
})
|
|
321
321
|
|
|
322
|
-
assert.equal(p1Items.length, 2)
|
|
323
|
-
assert.equal(p1Items[0].name, 'Item 1')
|
|
324
|
-
assert.equal(p1Items[1].name, 'Item 3')
|
|
322
|
+
assert.equal(p1Items.length, 2)
|
|
323
|
+
assert.equal(p1Items[0].name, 'Item 1')
|
|
324
|
+
assert.equal(p1Items[1].name, 'Item 3')
|
|
325
325
|
|
|
326
326
|
// Verify project 2 has items in correct order
|
|
327
327
|
const { data: p2Items } = await api.resources.items.query({
|
|
@@ -329,17 +329,17 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
329
329
|
filters: { project: project2.id, status: 'active' },
|
|
330
330
|
sort: ['position']
|
|
331
331
|
}
|
|
332
|
-
})
|
|
332
|
+
})
|
|
333
333
|
|
|
334
|
-
assert.equal(p2Items.length, 2)
|
|
335
|
-
assert.equal(p2Items[0].name, 'Item 2')
|
|
336
|
-
assert.equal(p2Items[1].name, 'Item 4')
|
|
334
|
+
assert.equal(p2Items.length, 2)
|
|
335
|
+
assert.equal(p2Items[0].name, 'Item 2')
|
|
336
|
+
assert.equal(p2Items[1].name, 'Item 4')
|
|
337
337
|
|
|
338
338
|
// Change status of item3 to archived
|
|
339
339
|
await api.resources.items.patch({
|
|
340
340
|
id: item3.id,
|
|
341
341
|
status: 'archived'
|
|
342
|
-
})
|
|
342
|
+
})
|
|
343
343
|
|
|
344
344
|
// Verify it's now in a different filter group
|
|
345
345
|
const { data: p1ActiveItems } = await api.resources.items.query({
|
|
@@ -347,25 +347,25 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
347
347
|
filters: { project: project1.id, status: 'active' },
|
|
348
348
|
sort: ['position']
|
|
349
349
|
}
|
|
350
|
-
})
|
|
350
|
+
})
|
|
351
351
|
|
|
352
|
-
assert.equal(p1ActiveItems.length, 1)
|
|
353
|
-
assert.equal(p1ActiveItems[0].name, 'Item 1')
|
|
352
|
+
assert.equal(p1ActiveItems.length, 1)
|
|
353
|
+
assert.equal(p1ActiveItems[0].name, 'Item 1')
|
|
354
354
|
|
|
355
355
|
const { data: p1ArchivedItems } = await api.resources.items.query({
|
|
356
356
|
queryParams: {
|
|
357
357
|
filters: { project: project1.id, status: 'archived' },
|
|
358
358
|
sort: ['position']
|
|
359
359
|
}
|
|
360
|
-
})
|
|
360
|
+
})
|
|
361
361
|
|
|
362
|
-
assert.equal(p1ArchivedItems.length, 1)
|
|
363
|
-
assert.equal(p1ArchivedItems[0].name, 'Item 3')
|
|
362
|
+
assert.equal(p1ArchivedItems.length, 1)
|
|
363
|
+
assert.equal(p1ArchivedItems[0].name, 'Item 3')
|
|
364
364
|
// Position should be maintained when moving to new filter group
|
|
365
365
|
// (the plugin doesn't know where to position it in the new group)
|
|
366
|
-
assert(p1ArchivedItems[0].position)
|
|
367
|
-
})
|
|
368
|
-
})
|
|
366
|
+
assert(p1ArchivedItems[0].position) // Just verify it has a position
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
369
|
|
|
370
370
|
describe('Complex Multi-Filter Scenarios', () => {
|
|
371
371
|
it('should handle three or more filter fields', async () => {
|
|
@@ -373,7 +373,7 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
373
373
|
const multiApi = await createPositioningApi(knex, {
|
|
374
374
|
apiName: 'multi-filter-three',
|
|
375
375
|
tablePrefix: 'mf3'
|
|
376
|
-
})
|
|
376
|
+
})
|
|
377
377
|
|
|
378
378
|
// Priority field is already in the items schema
|
|
379
379
|
|
|
@@ -381,14 +381,14 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
381
381
|
field: 'position',
|
|
382
382
|
filters: ['project', 'status', 'priority'],
|
|
383
383
|
defaultPosition: 'last'
|
|
384
|
-
})
|
|
384
|
+
})
|
|
385
385
|
|
|
386
|
-
const project = await multiApi.resources.projects.post({ name: 'Project' })
|
|
386
|
+
const project = await multiApi.resources.projects.post({ name: 'Project' })
|
|
387
387
|
|
|
388
388
|
// Create items in different combinations
|
|
389
|
-
const items = []
|
|
390
|
-
const statuses = ['active', 'pending', 'archived']
|
|
391
|
-
const priorities = ['high', 'medium', 'low']
|
|
389
|
+
const items = []
|
|
390
|
+
const statuses = ['active', 'pending', 'archived']
|
|
391
|
+
const priorities = ['high', 'medium', 'low']
|
|
392
392
|
|
|
393
393
|
for (const status of statuses) {
|
|
394
394
|
for (const priority of priorities) {
|
|
@@ -397,8 +397,8 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
397
397
|
project: project.id,
|
|
398
398
|
status,
|
|
399
399
|
priority
|
|
400
|
-
})
|
|
401
|
-
items.push(item)
|
|
400
|
+
})
|
|
401
|
+
items.push(item)
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
|
|
@@ -407,83 +407,83 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
407
407
|
for (const priority of priorities) {
|
|
408
408
|
const { data: filtered } = await multiApi.resources.items.query({
|
|
409
409
|
queryParams: {
|
|
410
|
-
filters: {
|
|
411
|
-
project: project.id,
|
|
412
|
-
status
|
|
413
|
-
priority
|
|
410
|
+
filters: {
|
|
411
|
+
project: project.id,
|
|
412
|
+
status,
|
|
413
|
+
priority
|
|
414
414
|
},
|
|
415
415
|
sort: ['position']
|
|
416
416
|
}
|
|
417
|
-
})
|
|
417
|
+
})
|
|
418
418
|
|
|
419
|
-
assert.equal(filtered.length, 1)
|
|
420
|
-
assert.equal(filtered[0].name, `Item ${status}-${priority}`)
|
|
421
|
-
assert.equal(filtered[0].position, 'a0')
|
|
419
|
+
assert.equal(filtered.length, 1)
|
|
420
|
+
assert.equal(filtered[0].name, `Item ${status}-${priority}`)
|
|
421
|
+
assert.equal(filtered[0].position, 'a0') // Each is first in its group
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
// Add more items to one combination
|
|
426
|
-
const moreItems = []
|
|
426
|
+
const moreItems = []
|
|
427
427
|
for (let i = 2; i <= 5; i++) {
|
|
428
428
|
const item = await multiApi.resources.items.post({
|
|
429
429
|
name: `Item active-high ${i}`,
|
|
430
430
|
project: project.id,
|
|
431
431
|
status: 'active',
|
|
432
432
|
priority: 'high'
|
|
433
|
-
})
|
|
434
|
-
moreItems.push(item)
|
|
433
|
+
})
|
|
434
|
+
moreItems.push(item)
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
// Verify the active-high combination now has 5 items
|
|
438
438
|
const { data: activeHighItems } = await multiApi.resources.items.query({
|
|
439
439
|
queryParams: {
|
|
440
|
-
filters: {
|
|
441
|
-
project: project.id,
|
|
440
|
+
filters: {
|
|
441
|
+
project: project.id,
|
|
442
442
|
status: 'active',
|
|
443
|
-
priority: 'high'
|
|
443
|
+
priority: 'high'
|
|
444
444
|
},
|
|
445
445
|
sort: ['position']
|
|
446
446
|
}
|
|
447
|
-
})
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
assert.equal(activeHighItems.length, 5)
|
|
448
450
|
|
|
449
|
-
assert.equal(activeHighItems.length, 5);
|
|
450
|
-
|
|
451
451
|
// Verify they're in order
|
|
452
452
|
for (let i = 1; i < activeHighItems.length; i++) {
|
|
453
|
-
assert(activeHighItems[i-1].position < activeHighItems[i].position)
|
|
453
|
+
assert(activeHighItems[i - 1].position < activeHighItems[i].position)
|
|
454
454
|
}
|
|
455
|
-
})
|
|
455
|
+
})
|
|
456
456
|
|
|
457
457
|
it('should handle partial filter queries correctly', async () => {
|
|
458
458
|
// Plugin already installed with filters in before hook
|
|
459
459
|
|
|
460
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
461
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
460
|
+
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
461
|
+
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
462
462
|
|
|
463
463
|
// Create items
|
|
464
464
|
await api.resources.items.post({
|
|
465
465
|
name: 'P1 Active 1',
|
|
466
466
|
project: project1.id,
|
|
467
467
|
status: 'active'
|
|
468
|
-
})
|
|
468
|
+
})
|
|
469
469
|
|
|
470
470
|
await api.resources.items.post({
|
|
471
471
|
name: 'P1 Active 2',
|
|
472
472
|
project: project1.id,
|
|
473
473
|
status: 'active'
|
|
474
|
-
})
|
|
474
|
+
})
|
|
475
475
|
|
|
476
476
|
await api.resources.items.post({
|
|
477
477
|
name: 'P1 Archived',
|
|
478
478
|
project: project1.id,
|
|
479
479
|
status: 'archived'
|
|
480
|
-
})
|
|
480
|
+
})
|
|
481
481
|
|
|
482
482
|
await api.resources.items.post({
|
|
483
483
|
name: 'P2 Active',
|
|
484
484
|
project: project2.id,
|
|
485
485
|
status: 'active'
|
|
486
|
-
})
|
|
486
|
+
})
|
|
487
487
|
|
|
488
488
|
// Query with only one filter (should return items from multiple position groups)
|
|
489
489
|
const { data: activeItems } = await api.resources.items.query({
|
|
@@ -491,36 +491,36 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
491
491
|
filters: { status: 'active' },
|
|
492
492
|
sort: ['position']
|
|
493
493
|
}
|
|
494
|
-
})
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
assert.equal(activeItems.length, 3)
|
|
495
497
|
|
|
496
|
-
assert.equal(activeItems.length, 3);
|
|
497
|
-
|
|
498
498
|
// Items from different projects might have same position
|
|
499
|
-
const p1ActiveItems = activeItems.filter(i => i.name.startsWith('P1'))
|
|
500
|
-
const p2ActiveItems = activeItems.filter(i => i.name.startsWith('P2'))
|
|
501
|
-
|
|
502
|
-
assert.equal(p1ActiveItems.length, 2)
|
|
503
|
-
assert.equal(p2ActiveItems.length, 1)
|
|
504
|
-
|
|
499
|
+
const p1ActiveItems = activeItems.filter(i => i.name.startsWith('P1'))
|
|
500
|
+
const p2ActiveItems = activeItems.filter(i => i.name.startsWith('P2'))
|
|
501
|
+
|
|
502
|
+
assert.equal(p1ActiveItems.length, 2)
|
|
503
|
+
assert.equal(p2ActiveItems.length, 1)
|
|
504
|
+
|
|
505
505
|
// Within same project, positions should be unique and ordered
|
|
506
|
-
assert(p1ActiveItems[0].position < p1ActiveItems[1].position)
|
|
507
|
-
})
|
|
506
|
+
assert(p1ActiveItems[0].position < p1ActiveItems[1].position)
|
|
507
|
+
})
|
|
508
508
|
|
|
509
509
|
it('should handle filter changes during bulk operations', async () => {
|
|
510
510
|
// Plugin already installed with filters in before hook
|
|
511
511
|
|
|
512
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
513
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
512
|
+
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
513
|
+
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
514
514
|
|
|
515
515
|
// Create multiple items
|
|
516
|
-
const items = []
|
|
516
|
+
const items = []
|
|
517
517
|
for (let i = 1; i <= 10; i++) {
|
|
518
518
|
const item = await api.resources.items.post({
|
|
519
519
|
name: `Item ${i}`,
|
|
520
520
|
project: project1.id,
|
|
521
521
|
status: 'active'
|
|
522
|
-
})
|
|
523
|
-
items.push(item)
|
|
522
|
+
})
|
|
523
|
+
items.push(item)
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
// Move multiple items to different filter groups concurrently
|
|
@@ -529,20 +529,20 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
529
529
|
api.resources.items.patch({ id: items[0].id, status: 'archived' }),
|
|
530
530
|
api.resources.items.patch({ id: items[1].id, status: 'archived' }),
|
|
531
531
|
api.resources.items.patch({ id: items[2].id, status: 'archived' }),
|
|
532
|
-
|
|
532
|
+
|
|
533
533
|
// Move to project 2
|
|
534
534
|
api.resources.items.patch({ id: items[3].id, project: project2.id }),
|
|
535
535
|
api.resources.items.patch({ id: items[4].id, project: project2.id }),
|
|
536
|
-
|
|
536
|
+
|
|
537
537
|
// Move to project 2 and archived
|
|
538
|
-
api.resources.items.patch({
|
|
539
|
-
id: items[5].id,
|
|
540
|
-
project: project2.id,
|
|
541
|
-
status: 'archived'
|
|
538
|
+
api.resources.items.patch({
|
|
539
|
+
id: items[5].id,
|
|
540
|
+
project: project2.id,
|
|
541
|
+
status: 'archived'
|
|
542
542
|
})
|
|
543
|
-
]
|
|
543
|
+
]
|
|
544
544
|
|
|
545
|
-
await Promise.all(updates)
|
|
545
|
+
await Promise.all(updates)
|
|
546
546
|
|
|
547
547
|
// Verify distributions
|
|
548
548
|
const { data: p1Active } = await api.resources.items.query({
|
|
@@ -550,73 +550,73 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
550
550
|
filters: { project: project1.id, status: 'active' },
|
|
551
551
|
sort: ['position']
|
|
552
552
|
}
|
|
553
|
-
})
|
|
554
|
-
assert.equal(p1Active.length, 4)
|
|
553
|
+
})
|
|
554
|
+
assert.equal(p1Active.length, 4) // 10 - 3 archived - 3 moved to p2
|
|
555
555
|
|
|
556
556
|
const { data: p1Archived } = await api.resources.items.query({
|
|
557
557
|
queryParams: {
|
|
558
558
|
filters: { project: project1.id, status: 'archived' },
|
|
559
559
|
sort: ['position']
|
|
560
560
|
}
|
|
561
|
-
})
|
|
562
|
-
assert.equal(p1Archived.length, 3)
|
|
561
|
+
})
|
|
562
|
+
assert.equal(p1Archived.length, 3)
|
|
563
563
|
|
|
564
564
|
const { data: p2Active } = await api.resources.items.query({
|
|
565
565
|
queryParams: {
|
|
566
566
|
filters: { project: project2.id, status: 'active' },
|
|
567
567
|
sort: ['position']
|
|
568
568
|
}
|
|
569
|
-
})
|
|
570
|
-
assert.equal(p2Active.length, 2)
|
|
569
|
+
})
|
|
570
|
+
assert.equal(p2Active.length, 2)
|
|
571
571
|
|
|
572
572
|
const { data: p2Archived } = await api.resources.items.query({
|
|
573
573
|
queryParams: {
|
|
574
574
|
filters: { project: project2.id, status: 'archived' },
|
|
575
575
|
sort: ['position']
|
|
576
576
|
}
|
|
577
|
-
})
|
|
578
|
-
assert.equal(p2Archived.length, 1)
|
|
577
|
+
})
|
|
578
|
+
assert.equal(p2Archived.length, 1)
|
|
579
579
|
|
|
580
580
|
// Verify all groups have proper positioning
|
|
581
|
-
const allGroups = [p1Active, p1Archived, p2Active, p2Archived]
|
|
581
|
+
const allGroups = [p1Active, p1Archived, p2Active, p2Archived]
|
|
582
582
|
for (const group of allGroups) {
|
|
583
583
|
if (group.length > 0) {
|
|
584
584
|
// All items should have positions and be properly ordered
|
|
585
585
|
for (let i = 0; i < group.length; i++) {
|
|
586
|
-
assert(group[i].position, `Item ${i} should have a position`)
|
|
586
|
+
assert(group[i].position, `Item ${i} should have a position`)
|
|
587
587
|
if (i > 0) {
|
|
588
|
-
assert(group[i-1].position < group[i].position,
|
|
588
|
+
assert(group[i - 1].position < group[i].position, 'Items should be ordered')
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
592
|
}
|
|
593
|
-
})
|
|
594
|
-
})
|
|
593
|
+
})
|
|
594
|
+
})
|
|
595
595
|
|
|
596
596
|
describe('Edge Cases with Multi-Filter', () => {
|
|
597
597
|
it('should handle empty string vs null in filters', async () => {
|
|
598
598
|
// Plugin already installed with filters in before hook
|
|
599
599
|
|
|
600
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
600
|
+
const project = await api.resources.projects.post({ name: 'Project' })
|
|
601
601
|
|
|
602
602
|
// Create items with various empty/null combinations
|
|
603
603
|
const item1 = await api.resources.items.post({
|
|
604
604
|
name: 'Item 1',
|
|
605
605
|
project: project.id,
|
|
606
606
|
status: '' // Empty string
|
|
607
|
-
})
|
|
607
|
+
})
|
|
608
608
|
|
|
609
609
|
const item2 = await api.resources.items.post({
|
|
610
610
|
name: 'Item 2',
|
|
611
611
|
project: project.id,
|
|
612
612
|
status: null // Null
|
|
613
|
-
})
|
|
613
|
+
})
|
|
614
614
|
|
|
615
615
|
const item3 = await api.resources.items.post({
|
|
616
616
|
name: 'Item 3',
|
|
617
617
|
project: project.id,
|
|
618
618
|
status: 'active'
|
|
619
|
-
})
|
|
619
|
+
})
|
|
620
620
|
|
|
621
621
|
// Query with empty string filter
|
|
622
622
|
const { data: emptyStatusItems } = await api.resources.items.query({
|
|
@@ -624,10 +624,10 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
624
624
|
filters: { project: project.id, status: '' },
|
|
625
625
|
sort: ['position']
|
|
626
626
|
}
|
|
627
|
-
})
|
|
627
|
+
})
|
|
628
628
|
|
|
629
|
-
assert.equal(emptyStatusItems.length, 1)
|
|
630
|
-
assert.equal(emptyStatusItems[0].name, 'Item 1')
|
|
629
|
+
assert.equal(emptyStatusItems.length, 1)
|
|
630
|
+
assert.equal(emptyStatusItems[0].name, 'Item 1')
|
|
631
631
|
|
|
632
632
|
// Query with null filter
|
|
633
633
|
const { data: nullStatusItems } = await api.resources.items.query({
|
|
@@ -635,40 +635,40 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
635
635
|
filters: { project: project.id, status: null },
|
|
636
636
|
sort: ['position']
|
|
637
637
|
}
|
|
638
|
-
})
|
|
638
|
+
})
|
|
639
639
|
|
|
640
|
-
assert.equal(nullStatusItems.length, 1)
|
|
641
|
-
assert.equal(nullStatusItems[0].name, 'Item 2')
|
|
640
|
+
assert.equal(nullStatusItems.length, 1)
|
|
641
|
+
assert.equal(nullStatusItems[0].name, 'Item 2')
|
|
642
642
|
|
|
643
643
|
// Verify they're in different position groups
|
|
644
|
-
assert.equal(item1.position, 'a0')
|
|
645
|
-
assert.equal(item2.position, 'a0')
|
|
646
|
-
assert.equal(item3.position, 'a0')
|
|
647
|
-
})
|
|
644
|
+
assert.equal(item1.position, 'a0')
|
|
645
|
+
assert.equal(item2.position, 'a0')
|
|
646
|
+
assert.equal(item3.position, 'a0')
|
|
647
|
+
})
|
|
648
648
|
|
|
649
649
|
it('should handle numeric vs string filter values', async () => {
|
|
650
650
|
// Plugin already installed with filters in before hook
|
|
651
651
|
|
|
652
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
652
|
+
const project = await api.resources.projects.post({ name: 'Project' })
|
|
653
653
|
|
|
654
654
|
// Create items with numeric-like status values
|
|
655
655
|
const item1 = await api.resources.items.post({
|
|
656
656
|
name: 'Item 1',
|
|
657
657
|
project: project.id,
|
|
658
658
|
status: '1'
|
|
659
|
-
})
|
|
659
|
+
})
|
|
660
660
|
|
|
661
661
|
const item2 = await api.resources.items.post({
|
|
662
662
|
name: 'Item 2',
|
|
663
663
|
project: project.id,
|
|
664
664
|
status: '01' // Different string representation
|
|
665
|
-
})
|
|
665
|
+
})
|
|
666
666
|
|
|
667
667
|
const item3 = await api.resources.items.post({
|
|
668
668
|
name: 'Item 3',
|
|
669
669
|
project: project.id,
|
|
670
670
|
status: '1'
|
|
671
|
-
})
|
|
671
|
+
})
|
|
672
672
|
|
|
673
673
|
// Query with string '1'
|
|
674
674
|
const { data: status1Items } = await api.resources.items.query({
|
|
@@ -676,12 +676,12 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
676
676
|
filters: { project: project.id, status: '1' },
|
|
677
677
|
sort: ['position']
|
|
678
678
|
}
|
|
679
|
-
})
|
|
679
|
+
})
|
|
680
680
|
|
|
681
|
-
assert.equal(status1Items.length, 2)
|
|
682
|
-
assert.equal(status1Items[0].name, 'Item 1')
|
|
683
|
-
assert.equal(status1Items[1].name, 'Item 3')
|
|
684
|
-
assert(status1Items[0].position < status1Items[1].position)
|
|
681
|
+
assert.equal(status1Items.length, 2)
|
|
682
|
+
assert.equal(status1Items[0].name, 'Item 1')
|
|
683
|
+
assert.equal(status1Items[1].name, 'Item 3')
|
|
684
|
+
assert(status1Items[0].position < status1Items[1].position)
|
|
685
685
|
|
|
686
686
|
// Query with string '01'
|
|
687
687
|
const { data: status01Items } = await api.resources.items.query({
|
|
@@ -689,11 +689,11 @@ describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
|
689
689
|
filters: { project: project.id, status: '01' },
|
|
690
690
|
sort: ['position']
|
|
691
691
|
}
|
|
692
|
-
})
|
|
693
|
-
|
|
694
|
-
assert.equal(status01Items.length, 1)
|
|
695
|
-
assert.equal(status01Items[0].name, 'Item 2')
|
|
696
|
-
assert.equal(status01Items[0].position, 'a0')
|
|
697
|
-
})
|
|
698
|
-
})
|
|
699
|
-
})
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
assert.equal(status01Items.length, 1)
|
|
695
|
+
assert.equal(status01Items[0].name, 'Item 2')
|
|
696
|
+
assert.equal(status01Items[0].position, 'a0')
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
})
|