velocious 1.0.652 → 1.0.654
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/README.md +14 -1
- package/build/database/query/model-class-query.js +3 -1
- package/build/database/query/where-hash.js +12 -1
- package/build/database/query/where-in.js +48 -0
- package/build/database/query/where-model-class-hash.js +34 -1
- package/build/environment-handlers/node/cli/commands/test.js +2 -1
- package/build/src/database/query/model-class-query.js +5 -2
- package/build/src/database/query/where-hash.d.ts.map +1 -1
- package/build/src/database/query/where-hash.js +14 -2
- package/build/src/database/query/where-in.d.ts +22 -0
- package/build/src/database/query/where-in.d.ts.map +1 -0
- package/build/src/database/query/where-in.js +40 -0
- package/build/src/database/query/where-model-class-hash.d.ts +11 -0
- package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
- package/build/src/database/query/where-model-class-hash.js +33 -2
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +4 -2
- package/build/src/testing/test-runner.d.ts +23 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +29 -2
- package/build/src/testing/velocious-runner-reporter.d.ts +7 -0
- package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -1
- package/build/src/testing/velocious-runner-reporter.js +54 -12
- package/build/testing/test-runner.js +33 -1
- package/build/testing/velocious-runner-reporter.js +52 -11
- package/package.json +4 -4
- package/scripts/browser-test-session.js +21 -23
- package/src/database/query/model-class-query.js +3 -1
- package/src/database/query/where-hash.js +12 -1
- package/src/database/query/where-in.js +48 -0
- package/src/database/query/where-model-class-hash.js +34 -1
- package/src/environment-handlers/node/cli/commands/test.js +2 -1
- package/src/testing/test-runner.js +33 -1
- package/src/testing/velocious-runner-reporter.js +52 -11
package/README.md
CHANGED
|
@@ -178,7 +178,7 @@ Baselines are generated against a fresh checkout (no generated dummy `configurat
|
|
|
178
178
|
# Testing
|
|
179
179
|
|
|
180
180
|
Application tests may import the testing DSL from the independent public package.
|
|
181
|
-
`@velocious/testing` `0.0.
|
|
181
|
+
`@velocious/testing` `0.0.12` is the declaration and execution engine. Compatible
|
|
182
182
|
installed copies share one protocol-1/schema-3 default registry. Velocious adapts each
|
|
183
183
|
package-owned attempt with its database, request, profiling, and cleanup behavior; the
|
|
184
184
|
existing Velocious facade exports the same declaration DSL and remains supported.
|
|
@@ -327,6 +327,8 @@ Use `consoleOutput: "live"` to preserve the previous passthrough behavior where
|
|
|
327
327
|
|
|
328
328
|
Listen for attempt and retry events if you need to reset shared state after a failed attempt or log retry lifecycle details. `testAttemptFailed` fires after every failed attempt, including the final failed attempt when no retries remain. `testRetrying` only fires before a retry, and `testFailed` only fires after retries are exhausted.
|
|
329
329
|
|
|
330
|
+
A shared resource owner may throw an error carrying `terminalResource: {scope: "run", name: "shared-resource"}`. With a compatible `@velocious/testing` runner, Velocious preserves this contract through causes and aggregate errors, reports the originating failure with full causal stacks, suppresses retries, and emits `testNotRun` for later selected cases instead of running their callbacks. `testNotRun` receives `{configuration, test, testRunner}`; `test` has `status: "not-run"`, `fullName`, `location`, and the originating `reason`. `testRunner.getNotRunTests()` and `getNotRunTestDetails()` expose this separate accounting. Entered suites clean up once; the CLI exits unsuccessfully even for terminal setup with zero executed cases. Unmatched filters retain their explanatory message and `no-tests` profile status, independently of terminal setup failures. This does not restart a browser or cancel already-issued work. Upgrade the shared runner and this reporter adapter together before enabling terminal resource owners; earlier shared runners ignore the marker. See [terminal resource reporting](docs/terminal-resource-reporting.md).
|
|
331
|
+
|
|
330
332
|
```js
|
|
331
333
|
import {testEvents} from "velocious/build/src/testing/test.js"
|
|
332
334
|
|
|
@@ -1683,9 +1685,20 @@ const tasks = await Task.all().toArray()
|
|
|
1683
1685
|
|
|
1684
1686
|
### Filtering
|
|
1685
1687
|
|
|
1688
|
+
Use `{in: [...]}` for explicit null-aware membership. Mixed lists match either
|
|
1689
|
+
IN members or SQL NULL, grouped locally so sibling and chained filters still
|
|
1690
|
+
apply. Null-only lists use IS NULL; empty lists match nothing. Direct arrays keep
|
|
1691
|
+
their existing driver-specific behavior, including string-null quoting and
|
|
1692
|
+
column collation. See [Query filtering](docs/query-filtering.md) for
|
|
1693
|
+
validation, normalization, negation, relationships and raw-query boundaries.
|
|
1694
|
+
|
|
1686
1695
|
```js
|
|
1687
1696
|
const tasks = await Task.where({status: "open"}).toArray()
|
|
1688
1697
|
|
|
1698
|
+
const nullOrManualTasks = await Task
|
|
1699
|
+
.where({projectId: project.id(), description: {in: [null, "manual"]}})
|
|
1700
|
+
.toArray()
|
|
1701
|
+
|
|
1689
1702
|
const tasksForActiveProjects = await Task.where({
|
|
1690
1703
|
project: {projectDetail: {isActive: true}}
|
|
1691
1704
|
}).toArray()
|
|
@@ -1468,7 +1468,9 @@ function splitWhereHash({hash, modelClass}) {
|
|
|
1468
1468
|
const isNested = isPlainObject(value)
|
|
1469
1469
|
const relationship = getRelationshipByName(modelClass, key)
|
|
1470
1470
|
|
|
1471
|
-
if (isNested) {
|
|
1471
|
+
if (isNested && !relationship && resolveColumnName(modelClass, key)) {
|
|
1472
|
+
resolvedHash[key] = value
|
|
1473
|
+
} else if (isNested) {
|
|
1472
1474
|
if (relationship) {
|
|
1473
1475
|
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
1474
1476
|
if (!rawTargetModelClass) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import WhereBase from "./where-base.js"
|
|
4
|
+
import WhereIn from "./where-in.js"
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* VelociousDatabaseQueryWhereHash class.
|
|
@@ -50,7 +51,17 @@ export default class VelociousDatabaseQueryWhereHash extends WhereBase {
|
|
|
50
51
|
if (index > 0) sql += " AND "
|
|
51
52
|
sql += "1=0"
|
|
52
53
|
} else if (!Array.isArray(whereValue) && whereValue !== null && typeof whereValue == "object") {
|
|
53
|
-
|
|
54
|
+
if (tableName && "in" in whereValue) {
|
|
55
|
+
if (index > 0) sql += " AND "
|
|
56
|
+
|
|
57
|
+
sql += WhereIn.toSql({
|
|
58
|
+
columnSql: `${options.quoteTableName(tableName)}.${options.quoteColumnName(whereKey)}`,
|
|
59
|
+
options,
|
|
60
|
+
values: WhereIn.values(whereValue)
|
|
61
|
+
})
|
|
62
|
+
} else {
|
|
63
|
+
sql += this._whereSQLFromHash(whereValue, whereKey, index)
|
|
64
|
+
}
|
|
54
65
|
} else {
|
|
55
66
|
if (index > 0) sql += " AND "
|
|
56
67
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {string | number | boolean | null} InValue */
|
|
4
|
+
|
|
5
|
+
export default class WhereIn {
|
|
6
|
+
/**
|
|
7
|
+
* Validates an explicit membership descriptor at a column boundary.
|
|
8
|
+
* @param {unknown} condition - Untrusted column condition, narrowed before use.
|
|
9
|
+
* @returns {InValue[]} - Validated members in a new array.
|
|
10
|
+
*/
|
|
11
|
+
static values(condition) {
|
|
12
|
+
if (condition === null || typeof condition !== "object" ||
|
|
13
|
+
!Object.hasOwn(condition, "in") || !("in" in condition) ||
|
|
14
|
+
Reflect.ownKeys(condition).length !== 1 || !Array.isArray(condition.in)) {
|
|
15
|
+
throw new Error("Invalid IN condition: expected an object with only an own 'in' array")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @type {InValue[]} */
|
|
19
|
+
const values = []
|
|
20
|
+
|
|
21
|
+
for (const value of condition.in) {
|
|
22
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" &&
|
|
23
|
+
!(typeof value === "number" && Number.isFinite(value))) {
|
|
24
|
+
throw new Error("Invalid IN condition: members must be strings, finite numbers, booleans or null")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
values.push(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return values
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders membership without letting its null branch escape sibling filters.
|
|
35
|
+
* @param {{columnSql: string, inColumnSql?: string, values: InValue[], options: import("../query-parser/options.js").default}} args - Quoted column operands, normalized members and driver quoting.
|
|
36
|
+
* @returns {string} - Complete membership predicate.
|
|
37
|
+
*/
|
|
38
|
+
static toSql({columnSql, inColumnSql = columnSql, values, options}) {
|
|
39
|
+
const nonNullValues = values.filter((value) => value !== null)
|
|
40
|
+
const includesNull = values.includes(null)
|
|
41
|
+
|
|
42
|
+
if (nonNullValues.length === 0) return includesNull ? `${columnSql} IS NULL` : "1=0"
|
|
43
|
+
|
|
44
|
+
const membershipSql = `${inColumnSql} IN (${nonNullValues.map((value) => options.quote(value)).join(", ")})`
|
|
45
|
+
|
|
46
|
+
return includesNull ? `(${membershipSql} OR ${columnSql} IS NULL)` : membershipSql
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as inflection from "inflection"
|
|
4
4
|
import {isPlainObject} from "is-plain-object"
|
|
5
5
|
import WhereBase from "./where-base.js"
|
|
6
|
+
import WhereIn from "./where-in.js"
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* No match.
|
|
@@ -378,6 +379,34 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
|
|
|
378
379
|
return normalized
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Normalizes explicit membership through the model's column metadata.
|
|
384
|
+
* @param {{modelClass: typeof import("../record/index.js").default, columnName: string, tableName?: string, condition: unknown}} args - Resolved column and untrusted membership descriptor.
|
|
385
|
+
* @returns {string} - Complete column membership predicate.
|
|
386
|
+
*/
|
|
387
|
+
_whereSQLFromInCondition({modelClass, columnName, tableName, condition}) {
|
|
388
|
+
const options = this.getOptions()
|
|
389
|
+
const values = WhereIn.values(condition)
|
|
390
|
+
const normalizedValues = this._normalizeSqliteBooleanValue({columnName, modelClass, value: values})
|
|
391
|
+
/** @type {import("./where-in.js").InValue[] | typeof NO_MATCH} */
|
|
392
|
+
const typedValues = this._normalizeValueForColumnType({columnName, modelClass, value: normalizedValues})
|
|
393
|
+
|
|
394
|
+
if (typedValues === NO_MATCH) return "1=0"
|
|
395
|
+
|
|
396
|
+
const columnSql = tableName
|
|
397
|
+
? `${options.quoteTableName(tableName)}.${options.quoteColumnName(columnName)}`
|
|
398
|
+
: options.quoteColumnName(columnName)
|
|
399
|
+
const columnType = modelClass.getColumnTypeByName(columnName)
|
|
400
|
+
const castText = this.getQuery().driver.getType() === "mssql" && columnType?.toLowerCase() === "text"
|
|
401
|
+
|
|
402
|
+
return WhereIn.toSql({
|
|
403
|
+
columnSql,
|
|
404
|
+
inColumnSql: castText ? `CAST(${columnSql} AS NVARCHAR(MAX))` : columnSql,
|
|
405
|
+
options,
|
|
406
|
+
values: typedValues
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
381
410
|
/**
|
|
382
411
|
* Runs where sqlfrom hash.
|
|
383
412
|
* @param {WhereHash} hash - Hash.
|
|
@@ -400,7 +429,11 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
|
|
|
400
429
|
: null
|
|
401
430
|
const resolvedColumnName = this._resolveColumnName(modelClass, whereKey)
|
|
402
431
|
|
|
403
|
-
if (relationship &&
|
|
432
|
+
if (resolvedColumnName && !relationship && isPlainObject(whereValue)) {
|
|
433
|
+
if (index > 0) sql += " AND "
|
|
434
|
+
|
|
435
|
+
sql += this._whereSQLFromInCondition({columnName: resolvedColumnName, condition: whereValue, modelClass, tableName})
|
|
436
|
+
} else if (relationship && tuples) {
|
|
404
437
|
if (index > 0) sql += " AND "
|
|
405
438
|
|
|
406
439
|
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
@@ -203,7 +203,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
203
203
|
const hasExampleFilters = examplePatterns.length > 0
|
|
204
204
|
const hasTagFilters = includeTags.length > 0 || effectiveExcludeTagCount > 0
|
|
205
205
|
|
|
206
|
-
if ((hasTagFilters || hasLineFilters || hasExampleFilters) &&
|
|
206
|
+
if ((hasTagFilters || hasLineFilters || hasExampleFilters) && testRunner.hasNoMatches()) {
|
|
207
207
|
console.error(picocolors.red("\nNo tests matched the provided filters"))
|
|
208
208
|
await finalizeProfile("no-tests")
|
|
209
209
|
process.exit(1)
|
|
@@ -247,6 +247,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
if (testRunner.getNotRunTests() > 0) console.error(`${testRunner.getNotRunTests()} tests not run because a shared resource failed`)
|
|
250
251
|
console.error(picocolors.red(`\nTest run failed with ${testRunner.getFailedTests()} failed tests and ${testRunner.getSuccessfulTests()} successfull`))
|
|
251
252
|
await finalizeProfile("failed")
|
|
252
253
|
process.exit(1)
|