json-rest-api 1.0.28 → 2.0.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.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +746 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -174
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +20 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +142 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1069
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -385
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +38 -69
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +39 -82
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -150
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -118
- package/tests/fixtures/api-configs.js +0 -2509
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-cursor-multifield.test.js +0 -643
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,699 +0,0 @@
|
|
|
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
|
-
|
|
8
|
-
// Create Knex instance for tests
|
|
9
|
-
const knex = knexLib({
|
|
10
|
-
client: 'better-sqlite3',
|
|
11
|
-
connection: {
|
|
12
|
-
filename: ':memory:'
|
|
13
|
-
},
|
|
14
|
-
useNullAsDefault: true
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
describe('Multi-Filter Positioning Tests', { timeout: 30000 }, () => {
|
|
18
|
-
let api
|
|
19
|
-
|
|
20
|
-
before(async () => {
|
|
21
|
-
// Initialize API
|
|
22
|
-
api = await createPositioningApi(knex)
|
|
23
|
-
|
|
24
|
-
// Install positioning plugin with default configuration
|
|
25
|
-
await api.use(PositioningPlugin, {
|
|
26
|
-
field: 'position',
|
|
27
|
-
filters: ['project', 'status'], // Default filters for most tests
|
|
28
|
-
defaultPosition: 'last'
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
after(async () => {
|
|
33
|
-
await knex.destroy()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
beforeEach(async () => {
|
|
37
|
-
await cleanTables(knex, [
|
|
38
|
-
'positioning_categories',
|
|
39
|
-
'positioning_tasks',
|
|
40
|
-
'positioning_projects',
|
|
41
|
-
'positioning_items'
|
|
42
|
-
])
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe('Multiple Filter Fields', () => {
|
|
46
|
-
it('should maintain separate position sequences for each filter combination', async () => {
|
|
47
|
-
// Plugin already installed with filters in before hook
|
|
48
|
-
|
|
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' })
|
|
52
|
-
|
|
53
|
-
// Create items in different filter combinations
|
|
54
|
-
// Project 1, Active
|
|
55
|
-
const p1Active1 = await api.resources.items.post({
|
|
56
|
-
name: 'P1 Active 1',
|
|
57
|
-
project: project1.id,
|
|
58
|
-
status: 'active'
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const p1Active2 = await api.resources.items.post({
|
|
62
|
-
name: 'P1 Active 2',
|
|
63
|
-
project: project1.id,
|
|
64
|
-
status: 'active'
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
// Project 1, Archived
|
|
68
|
-
const p1Archived1 = await api.resources.items.post({
|
|
69
|
-
name: 'P1 Archived 1',
|
|
70
|
-
project: project1.id,
|
|
71
|
-
status: 'archived'
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const p1Archived2 = await api.resources.items.post({
|
|
75
|
-
name: 'P1 Archived 2',
|
|
76
|
-
project: project1.id,
|
|
77
|
-
status: 'archived'
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Project 2, Active
|
|
81
|
-
const p2Active1 = await api.resources.items.post({
|
|
82
|
-
name: 'P2 Active 1',
|
|
83
|
-
project: project2.id,
|
|
84
|
-
status: 'active'
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const p2Active2 = await api.resources.items.post({
|
|
88
|
-
name: 'P2 Active 2',
|
|
89
|
-
project: project2.id,
|
|
90
|
-
status: 'active'
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// Project 2, Archived
|
|
94
|
-
const p2Archived1 = await api.resources.items.post({
|
|
95
|
-
name: 'P2 Archived 1',
|
|
96
|
-
project: project2.id,
|
|
97
|
-
status: 'archived'
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
// Query each combination
|
|
101
|
-
const { data: p1ActiveItems } = await api.resources.items.query({
|
|
102
|
-
queryParams: {
|
|
103
|
-
filters: { project: project1.id, status: 'active' },
|
|
104
|
-
sort: ['position']
|
|
105
|
-
}
|
|
106
|
-
})
|
|
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')
|
|
111
|
-
|
|
112
|
-
const { data: p1ArchivedItems } = await api.resources.items.query({
|
|
113
|
-
queryParams: {
|
|
114
|
-
filters: { project: project1.id, status: 'archived' },
|
|
115
|
-
sort: ['position']
|
|
116
|
-
}
|
|
117
|
-
})
|
|
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')
|
|
122
|
-
|
|
123
|
-
const { data: p2ActiveItems } = await api.resources.items.query({
|
|
124
|
-
queryParams: {
|
|
125
|
-
filters: { project: project2.id, status: 'active' },
|
|
126
|
-
sort: ['position']
|
|
127
|
-
}
|
|
128
|
-
})
|
|
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')
|
|
133
|
-
|
|
134
|
-
// Verify positions are independent across filter combinations
|
|
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
|
-
})
|
|
141
|
-
|
|
142
|
-
it('should handle positioning with null values in multi-filter setup', async () => {
|
|
143
|
-
// Plugin already installed with filters in before hook
|
|
144
|
-
|
|
145
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
146
|
-
|
|
147
|
-
// Create items with various null combinations
|
|
148
|
-
const item1 = await api.resources.items.post({
|
|
149
|
-
name: 'Item 1',
|
|
150
|
-
project: project.id,
|
|
151
|
-
status: 'active'
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
const item2 = await api.resources.items.post({
|
|
155
|
-
name: 'Item 2',
|
|
156
|
-
project: project.id,
|
|
157
|
-
status: null // Null status
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
const item3 = await api.resources.items.post({
|
|
161
|
-
name: 'Item 3',
|
|
162
|
-
project: null, // Null project
|
|
163
|
-
status: 'active'
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const item4 = await api.resources.items.post({
|
|
167
|
-
name: 'Item 4',
|
|
168
|
-
project: null,
|
|
169
|
-
status: null // Both null
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
// Query different combinations
|
|
173
|
-
const { data: projectActiveItems } = await api.resources.items.query({
|
|
174
|
-
queryParams: {
|
|
175
|
-
filters: { project: project.id, status: 'active' },
|
|
176
|
-
sort: ['position']
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
assert.equal(projectActiveItems.length, 1)
|
|
180
|
-
assert.equal(projectActiveItems[0].name, 'Item 1')
|
|
181
|
-
|
|
182
|
-
const { data: projectNullStatusItems } = await api.resources.items.query({
|
|
183
|
-
queryParams: {
|
|
184
|
-
filters: { project: project.id, status: null },
|
|
185
|
-
sort: ['position']
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
assert.equal(projectNullStatusItems.length, 1)
|
|
189
|
-
assert.equal(projectNullStatusItems[0].name, 'Item 2')
|
|
190
|
-
|
|
191
|
-
const { data: nullProjectActiveItems } = await api.resources.items.query({
|
|
192
|
-
queryParams: {
|
|
193
|
-
filters: { project: null, status: 'active' },
|
|
194
|
-
sort: ['position']
|
|
195
|
-
}
|
|
196
|
-
})
|
|
197
|
-
assert.equal(nullProjectActiveItems.length, 1)
|
|
198
|
-
assert.equal(nullProjectActiveItems[0].name, 'Item 3')
|
|
199
|
-
|
|
200
|
-
const { data: bothNullItems } = await api.resources.items.query({
|
|
201
|
-
queryParams: {
|
|
202
|
-
filters: { project: null, status: null },
|
|
203
|
-
sort: ['position']
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
assert.equal(bothNullItems.length, 1)
|
|
207
|
-
assert.equal(bothNullItems[0].name, 'Item 4')
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
it('should handle beforeId correctly with multi-filter positioning', async () => {
|
|
211
|
-
// Plugin already installed with filters in before hook
|
|
212
|
-
|
|
213
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
214
|
-
|
|
215
|
-
// Create initial items
|
|
216
|
-
const item1 = await api.resources.items.post({
|
|
217
|
-
name: 'Item 1',
|
|
218
|
-
project: project.id,
|
|
219
|
-
status: 'active'
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
const item2 = await api.resources.items.post({
|
|
223
|
-
name: 'Item 2',
|
|
224
|
-
project: project.id,
|
|
225
|
-
status: 'active'
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
const item3 = await api.resources.items.post({
|
|
229
|
-
name: 'Item 3',
|
|
230
|
-
project: project.id,
|
|
231
|
-
status: 'active'
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// Create item in different filter group with beforeId from first group
|
|
235
|
-
const item4 = await api.resources.items.post({
|
|
236
|
-
name: 'Item 4',
|
|
237
|
-
project: project.id,
|
|
238
|
-
status: 'archived',
|
|
239
|
-
beforeId: item2.id // This should be ignored since it's in a different filter group
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
// Item 4 should be first in its filter group
|
|
243
|
-
const { data: archivedItems } = await api.resources.items.query({
|
|
244
|
-
queryParams: {
|
|
245
|
-
filters: { project: project.id, status: 'archived' },
|
|
246
|
-
sort: ['position']
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
|
-
|
|
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
|
-
|
|
254
|
-
// Now position an item before another in the same filter group
|
|
255
|
-
const item5 = await api.resources.items.post({
|
|
256
|
-
name: 'Item 5',
|
|
257
|
-
project: project.id,
|
|
258
|
-
status: 'active',
|
|
259
|
-
beforeId: item2.id
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
const { data: activeItems } = await api.resources.items.query({
|
|
263
|
-
queryParams: {
|
|
264
|
-
filters: { project: project.id, status: 'active' },
|
|
265
|
-
sort: ['position']
|
|
266
|
-
}
|
|
267
|
-
})
|
|
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
|
-
})
|
|
274
|
-
|
|
275
|
-
it('should handle updates that change filter values', async () => {
|
|
276
|
-
// Plugin already installed with filters in before hook
|
|
277
|
-
|
|
278
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
279
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
280
|
-
|
|
281
|
-
// Create items in project 1
|
|
282
|
-
const item1 = await api.resources.items.post({
|
|
283
|
-
name: 'Item 1',
|
|
284
|
-
project: project1.id,
|
|
285
|
-
status: 'active'
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
const item2 = await api.resources.items.post({
|
|
289
|
-
name: 'Item 2',
|
|
290
|
-
project: project1.id,
|
|
291
|
-
status: 'active'
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
const item3 = await api.resources.items.post({
|
|
295
|
-
name: 'Item 3',
|
|
296
|
-
project: project1.id,
|
|
297
|
-
status: 'active'
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
// Create items in project 2
|
|
301
|
-
const item4 = await api.resources.items.post({
|
|
302
|
-
name: 'Item 4',
|
|
303
|
-
project: project2.id,
|
|
304
|
-
status: 'active'
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
// Move item2 to project 2, positioning it before item4
|
|
308
|
-
await api.resources.items.patch({
|
|
309
|
-
id: item2.id,
|
|
310
|
-
project: project2.id,
|
|
311
|
-
beforeId: item4.id
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
// Verify project 1 now has 2 items
|
|
315
|
-
const { data: p1Items } = await api.resources.items.query({
|
|
316
|
-
queryParams: {
|
|
317
|
-
filters: { project: project1.id, status: 'active' },
|
|
318
|
-
sort: ['position']
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
assert.equal(p1Items.length, 2)
|
|
323
|
-
assert.equal(p1Items[0].name, 'Item 1')
|
|
324
|
-
assert.equal(p1Items[1].name, 'Item 3')
|
|
325
|
-
|
|
326
|
-
// Verify project 2 has items in correct order
|
|
327
|
-
const { data: p2Items } = await api.resources.items.query({
|
|
328
|
-
queryParams: {
|
|
329
|
-
filters: { project: project2.id, status: 'active' },
|
|
330
|
-
sort: ['position']
|
|
331
|
-
}
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
assert.equal(p2Items.length, 2)
|
|
335
|
-
assert.equal(p2Items[0].name, 'Item 2')
|
|
336
|
-
assert.equal(p2Items[1].name, 'Item 4')
|
|
337
|
-
|
|
338
|
-
// Change status of item3 to archived
|
|
339
|
-
await api.resources.items.patch({
|
|
340
|
-
id: item3.id,
|
|
341
|
-
status: 'archived'
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
// Verify it's now in a different filter group
|
|
345
|
-
const { data: p1ActiveItems } = await api.resources.items.query({
|
|
346
|
-
queryParams: {
|
|
347
|
-
filters: { project: project1.id, status: 'active' },
|
|
348
|
-
sort: ['position']
|
|
349
|
-
}
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
assert.equal(p1ActiveItems.length, 1)
|
|
353
|
-
assert.equal(p1ActiveItems[0].name, 'Item 1')
|
|
354
|
-
|
|
355
|
-
const { data: p1ArchivedItems } = await api.resources.items.query({
|
|
356
|
-
queryParams: {
|
|
357
|
-
filters: { project: project1.id, status: 'archived' },
|
|
358
|
-
sort: ['position']
|
|
359
|
-
}
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
assert.equal(p1ArchivedItems.length, 1)
|
|
363
|
-
assert.equal(p1ArchivedItems[0].name, 'Item 3')
|
|
364
|
-
// Position should be maintained when moving to new filter group
|
|
365
|
-
// (the plugin doesn't know where to position it in the new group)
|
|
366
|
-
assert(p1ArchivedItems[0].position) // Just verify it has a position
|
|
367
|
-
})
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
describe('Complex Multi-Filter Scenarios', () => {
|
|
371
|
-
it('should handle three or more filter fields', async () => {
|
|
372
|
-
// Create a new API instance for this test
|
|
373
|
-
const multiApi = await createPositioningApi(knex, {
|
|
374
|
-
apiName: 'multi-filter-three',
|
|
375
|
-
tablePrefix: 'mf3'
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
// Priority field is already in the items schema
|
|
379
|
-
|
|
380
|
-
await multiApi.use(PositioningPlugin, {
|
|
381
|
-
field: 'position',
|
|
382
|
-
filters: ['project', 'status', 'priority'],
|
|
383
|
-
defaultPosition: 'last'
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
const project = await multiApi.resources.projects.post({ name: 'Project' })
|
|
387
|
-
|
|
388
|
-
// Create items in different combinations
|
|
389
|
-
const items = []
|
|
390
|
-
const statuses = ['active', 'pending', 'archived']
|
|
391
|
-
const priorities = ['high', 'medium', 'low']
|
|
392
|
-
|
|
393
|
-
for (const status of statuses) {
|
|
394
|
-
for (const priority of priorities) {
|
|
395
|
-
const item = await multiApi.resources.items.post({
|
|
396
|
-
name: `Item ${status}-${priority}`,
|
|
397
|
-
project: project.id,
|
|
398
|
-
status,
|
|
399
|
-
priority
|
|
400
|
-
})
|
|
401
|
-
items.push(item)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Each combination should have its own sequence
|
|
406
|
-
for (const status of statuses) {
|
|
407
|
-
for (const priority of priorities) {
|
|
408
|
-
const { data: filtered } = await multiApi.resources.items.query({
|
|
409
|
-
queryParams: {
|
|
410
|
-
filters: {
|
|
411
|
-
project: project.id,
|
|
412
|
-
status,
|
|
413
|
-
priority
|
|
414
|
-
},
|
|
415
|
-
sort: ['position']
|
|
416
|
-
}
|
|
417
|
-
})
|
|
418
|
-
|
|
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
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Add more items to one combination
|
|
426
|
-
const moreItems = []
|
|
427
|
-
for (let i = 2; i <= 5; i++) {
|
|
428
|
-
const item = await multiApi.resources.items.post({
|
|
429
|
-
name: `Item active-high ${i}`,
|
|
430
|
-
project: project.id,
|
|
431
|
-
status: 'active',
|
|
432
|
-
priority: 'high'
|
|
433
|
-
})
|
|
434
|
-
moreItems.push(item)
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Verify the active-high combination now has 5 items
|
|
438
|
-
const { data: activeHighItems } = await multiApi.resources.items.query({
|
|
439
|
-
queryParams: {
|
|
440
|
-
filters: {
|
|
441
|
-
project: project.id,
|
|
442
|
-
status: 'active',
|
|
443
|
-
priority: 'high'
|
|
444
|
-
},
|
|
445
|
-
sort: ['position']
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
assert.equal(activeHighItems.length, 5)
|
|
450
|
-
|
|
451
|
-
// Verify they're in order
|
|
452
|
-
for (let i = 1; i < activeHighItems.length; i++) {
|
|
453
|
-
assert(activeHighItems[i - 1].position < activeHighItems[i].position)
|
|
454
|
-
}
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
it('should handle partial filter queries correctly', async () => {
|
|
458
|
-
// Plugin already installed with filters in before hook
|
|
459
|
-
|
|
460
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
461
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
462
|
-
|
|
463
|
-
// Create items
|
|
464
|
-
await api.resources.items.post({
|
|
465
|
-
name: 'P1 Active 1',
|
|
466
|
-
project: project1.id,
|
|
467
|
-
status: 'active'
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
await api.resources.items.post({
|
|
471
|
-
name: 'P1 Active 2',
|
|
472
|
-
project: project1.id,
|
|
473
|
-
status: 'active'
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
await api.resources.items.post({
|
|
477
|
-
name: 'P1 Archived',
|
|
478
|
-
project: project1.id,
|
|
479
|
-
status: 'archived'
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
await api.resources.items.post({
|
|
483
|
-
name: 'P2 Active',
|
|
484
|
-
project: project2.id,
|
|
485
|
-
status: 'active'
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
// Query with only one filter (should return items from multiple position groups)
|
|
489
|
-
const { data: activeItems } = await api.resources.items.query({
|
|
490
|
-
queryParams: {
|
|
491
|
-
filters: { status: 'active' },
|
|
492
|
-
sort: ['position']
|
|
493
|
-
}
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
assert.equal(activeItems.length, 3)
|
|
497
|
-
|
|
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
|
-
|
|
505
|
-
// Within same project, positions should be unique and ordered
|
|
506
|
-
assert(p1ActiveItems[0].position < p1ActiveItems[1].position)
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
it('should handle filter changes during bulk operations', async () => {
|
|
510
|
-
// Plugin already installed with filters in before hook
|
|
511
|
-
|
|
512
|
-
const project1 = await api.resources.projects.post({ name: 'Project 1' })
|
|
513
|
-
const project2 = await api.resources.projects.post({ name: 'Project 2' })
|
|
514
|
-
|
|
515
|
-
// Create multiple items
|
|
516
|
-
const items = []
|
|
517
|
-
for (let i = 1; i <= 10; i++) {
|
|
518
|
-
const item = await api.resources.items.post({
|
|
519
|
-
name: `Item ${i}`,
|
|
520
|
-
project: project1.id,
|
|
521
|
-
status: 'active'
|
|
522
|
-
})
|
|
523
|
-
items.push(item)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Move multiple items to different filter groups concurrently
|
|
527
|
-
const updates = [
|
|
528
|
-
// Move to archived
|
|
529
|
-
api.resources.items.patch({ id: items[0].id, status: 'archived' }),
|
|
530
|
-
api.resources.items.patch({ id: items[1].id, status: 'archived' }),
|
|
531
|
-
api.resources.items.patch({ id: items[2].id, status: 'archived' }),
|
|
532
|
-
|
|
533
|
-
// Move to project 2
|
|
534
|
-
api.resources.items.patch({ id: items[3].id, project: project2.id }),
|
|
535
|
-
api.resources.items.patch({ id: items[4].id, project: project2.id }),
|
|
536
|
-
|
|
537
|
-
// Move to project 2 and archived
|
|
538
|
-
api.resources.items.patch({
|
|
539
|
-
id: items[5].id,
|
|
540
|
-
project: project2.id,
|
|
541
|
-
status: 'archived'
|
|
542
|
-
})
|
|
543
|
-
]
|
|
544
|
-
|
|
545
|
-
await Promise.all(updates)
|
|
546
|
-
|
|
547
|
-
// Verify distributions
|
|
548
|
-
const { data: p1Active } = await api.resources.items.query({
|
|
549
|
-
queryParams: {
|
|
550
|
-
filters: { project: project1.id, status: 'active' },
|
|
551
|
-
sort: ['position']
|
|
552
|
-
}
|
|
553
|
-
})
|
|
554
|
-
assert.equal(p1Active.length, 4) // 10 - 3 archived - 3 moved to p2
|
|
555
|
-
|
|
556
|
-
const { data: p1Archived } = await api.resources.items.query({
|
|
557
|
-
queryParams: {
|
|
558
|
-
filters: { project: project1.id, status: 'archived' },
|
|
559
|
-
sort: ['position']
|
|
560
|
-
}
|
|
561
|
-
})
|
|
562
|
-
assert.equal(p1Archived.length, 3)
|
|
563
|
-
|
|
564
|
-
const { data: p2Active } = await api.resources.items.query({
|
|
565
|
-
queryParams: {
|
|
566
|
-
filters: { project: project2.id, status: 'active' },
|
|
567
|
-
sort: ['position']
|
|
568
|
-
}
|
|
569
|
-
})
|
|
570
|
-
assert.equal(p2Active.length, 2)
|
|
571
|
-
|
|
572
|
-
const { data: p2Archived } = await api.resources.items.query({
|
|
573
|
-
queryParams: {
|
|
574
|
-
filters: { project: project2.id, status: 'archived' },
|
|
575
|
-
sort: ['position']
|
|
576
|
-
}
|
|
577
|
-
})
|
|
578
|
-
assert.equal(p2Archived.length, 1)
|
|
579
|
-
|
|
580
|
-
// Verify all groups have proper positioning
|
|
581
|
-
const allGroups = [p1Active, p1Archived, p2Active, p2Archived]
|
|
582
|
-
for (const group of allGroups) {
|
|
583
|
-
if (group.length > 0) {
|
|
584
|
-
// All items should have positions and be properly ordered
|
|
585
|
-
for (let i = 0; i < group.length; i++) {
|
|
586
|
-
assert(group[i].position, `Item ${i} should have a position`)
|
|
587
|
-
if (i > 0) {
|
|
588
|
-
assert(group[i - 1].position < group[i].position, 'Items should be ordered')
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
})
|
|
594
|
-
})
|
|
595
|
-
|
|
596
|
-
describe('Edge Cases with Multi-Filter', () => {
|
|
597
|
-
it('should handle empty string vs null in filters', async () => {
|
|
598
|
-
// Plugin already installed with filters in before hook
|
|
599
|
-
|
|
600
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
601
|
-
|
|
602
|
-
// Create items with various empty/null combinations
|
|
603
|
-
const item1 = await api.resources.items.post({
|
|
604
|
-
name: 'Item 1',
|
|
605
|
-
project: project.id,
|
|
606
|
-
status: '' // Empty string
|
|
607
|
-
})
|
|
608
|
-
|
|
609
|
-
const item2 = await api.resources.items.post({
|
|
610
|
-
name: 'Item 2',
|
|
611
|
-
project: project.id,
|
|
612
|
-
status: null // Null
|
|
613
|
-
})
|
|
614
|
-
|
|
615
|
-
const item3 = await api.resources.items.post({
|
|
616
|
-
name: 'Item 3',
|
|
617
|
-
project: project.id,
|
|
618
|
-
status: 'active'
|
|
619
|
-
})
|
|
620
|
-
|
|
621
|
-
// Query with empty string filter
|
|
622
|
-
const { data: emptyStatusItems } = await api.resources.items.query({
|
|
623
|
-
queryParams: {
|
|
624
|
-
filters: { project: project.id, status: '' },
|
|
625
|
-
sort: ['position']
|
|
626
|
-
}
|
|
627
|
-
})
|
|
628
|
-
|
|
629
|
-
assert.equal(emptyStatusItems.length, 1)
|
|
630
|
-
assert.equal(emptyStatusItems[0].name, 'Item 1')
|
|
631
|
-
|
|
632
|
-
// Query with null filter
|
|
633
|
-
const { data: nullStatusItems } = await api.resources.items.query({
|
|
634
|
-
queryParams: {
|
|
635
|
-
filters: { project: project.id, status: null },
|
|
636
|
-
sort: ['position']
|
|
637
|
-
}
|
|
638
|
-
})
|
|
639
|
-
|
|
640
|
-
assert.equal(nullStatusItems.length, 1)
|
|
641
|
-
assert.equal(nullStatusItems[0].name, 'Item 2')
|
|
642
|
-
|
|
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
|
-
})
|
|
648
|
-
|
|
649
|
-
it('should handle numeric vs string filter values', async () => {
|
|
650
|
-
// Plugin already installed with filters in before hook
|
|
651
|
-
|
|
652
|
-
const project = await api.resources.projects.post({ name: 'Project' })
|
|
653
|
-
|
|
654
|
-
// Create items with numeric-like status values
|
|
655
|
-
const item1 = await api.resources.items.post({
|
|
656
|
-
name: 'Item 1',
|
|
657
|
-
project: project.id,
|
|
658
|
-
status: '1'
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
const item2 = await api.resources.items.post({
|
|
662
|
-
name: 'Item 2',
|
|
663
|
-
project: project.id,
|
|
664
|
-
status: '01' // Different string representation
|
|
665
|
-
})
|
|
666
|
-
|
|
667
|
-
const item3 = await api.resources.items.post({
|
|
668
|
-
name: 'Item 3',
|
|
669
|
-
project: project.id,
|
|
670
|
-
status: '1'
|
|
671
|
-
})
|
|
672
|
-
|
|
673
|
-
// Query with string '1'
|
|
674
|
-
const { data: status1Items } = await api.resources.items.query({
|
|
675
|
-
queryParams: {
|
|
676
|
-
filters: { project: project.id, status: '1' },
|
|
677
|
-
sort: ['position']
|
|
678
|
-
}
|
|
679
|
-
})
|
|
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)
|
|
685
|
-
|
|
686
|
-
// Query with string '01'
|
|
687
|
-
const { data: status01Items } = await api.resources.items.query({
|
|
688
|
-
queryParams: {
|
|
689
|
-
filters: { project: project.id, status: '01' },
|
|
690
|
-
sort: ['position']
|
|
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
|
-
})
|