@mitsuru793/vitest-sqlite 0.0.0-stage → 0.0.1-alpha.1
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 +139 -2
- package/dist/better-sqlite3.d.mts +11 -0
- package/dist/better-sqlite3.mjs +101 -0
- package/dist/index-DZsTinBh.d.mts +106 -0
- package/dist/matchers-8Jn4xSIt.mjs +189 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/vite-plus.d.mts +7 -0
- package/dist/vite-plus.mjs +1 -0
- package/dist/vitest-DjF_zZFj.d.mts +33 -0
- package/dist/vitest.d.mts +2 -0
- package/dist/vitest.mjs +6 -0
- package/package.json +54 -3
package/README.md
CHANGED
|
@@ -1,3 +1,140 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vitest-sqlite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vitest matchers and adapters for testing SQLite databases, with `better-sqlite3` support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the library and Vitest:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install --save-dev @mitsuru793/vitest-sqlite vitest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The `better-sqlite3` package is installed as a dependency of this library.
|
|
14
|
+
|
|
15
|
+
## Setup with Vitest
|
|
16
|
+
|
|
17
|
+
Import the Vitest integration once from a setup file:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// vitest.setup.ts
|
|
21
|
+
import "@mitsuru793/vitest-sqlite/vitest";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Register that file in your Vitest configuration:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// vitest.config.ts
|
|
28
|
+
import { defineConfig } from "vitest/config";
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
test: {
|
|
32
|
+
setupFiles: ["./vitest.setup.ts"],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The integration registers the matchers and adds their TypeScript types.
|
|
38
|
+
|
|
39
|
+
## Setup with Vite+
|
|
40
|
+
|
|
41
|
+
When running tests with Vite+, import the Vite+ integration instead:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// test/setup.ts
|
|
45
|
+
import "@mitsuru793/vitest-sqlite/vite-plus";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Add the setup file to the `test` configuration in `vite.config.ts`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { defineConfig } from "vite-plus";
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
test: {
|
|
55
|
+
setupFiles: ["./test/setup.ts"],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
Create a `better-sqlite3` database, wrap it in an adapter, and use `table()` to
|
|
63
|
+
create a reference for table-specific matchers:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import Database from "better-sqlite3";
|
|
67
|
+
import { expect, test } from "vitest";
|
|
68
|
+
import { BetterSqlite3Adapter, table } from "@mitsuru793/vitest-sqlite/better-sqlite3";
|
|
69
|
+
|
|
70
|
+
test("checks database tables and rows", () => {
|
|
71
|
+
const client = new Database(":memory:");
|
|
72
|
+
try {
|
|
73
|
+
client.exec(`
|
|
74
|
+
CREATE TABLE users (name TEXT, role TEXT);
|
|
75
|
+
INSERT INTO users VALUES ('Ada', 'admin');
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
const db = BetterSqlite3Adapter.create(client);
|
|
79
|
+
const users = table(db, "users");
|
|
80
|
+
|
|
81
|
+
expect(db).toHaveTable("users");
|
|
82
|
+
expect(users).toHaveColumn("name", { type: "TEXT" });
|
|
83
|
+
expect(users).toHaveRow({ name: "Ada", role: "admin" });
|
|
84
|
+
expect(users).toHaveRowCount(1);
|
|
85
|
+
expect(users).not.toBeEmptyTable();
|
|
86
|
+
} finally {
|
|
87
|
+
client.close();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Matchers
|
|
93
|
+
|
|
94
|
+
The matcher types are receiver-specific: table matchers are available for
|
|
95
|
+
`TableRef` values created by `table()`, while `toHaveTable` is available for
|
|
96
|
+
`DbAdapter` values.
|
|
97
|
+
|
|
98
|
+
### `expect(db).toHaveTable(name)`
|
|
99
|
+
|
|
100
|
+
checks whether a table exists.
|
|
101
|
+
|
|
102
|
+
### `expect(tableRef).toHaveColumn(name, { type? })`
|
|
103
|
+
|
|
104
|
+
checks for a column and, optionally, its SQLite type (`TEXT`, `INTEGER`, `REAL`, or `BLOB`).
|
|
105
|
+
|
|
106
|
+
The `unique` option checks for a single-column UNIQUE constraint or a
|
|
107
|
+
non-partial single-column unique index. Composite, partial, and expression
|
|
108
|
+
indexes do not make an individual column unique.
|
|
109
|
+
|
|
110
|
+
### `expect(tableRef).toHaveRow(where)`
|
|
111
|
+
|
|
112
|
+
checks whether at least one row matches.
|
|
113
|
+
|
|
114
|
+
### `expect(tableRef).toHaveRowCount(count, where?)`
|
|
115
|
+
|
|
116
|
+
checks the matching row count, or the total count if `where` is omitted.
|
|
117
|
+
|
|
118
|
+
### `expect(tableRef).toBeEmptyTable()`
|
|
119
|
+
|
|
120
|
+
checks that the table has no rows.
|
|
121
|
+
|
|
122
|
+
## `where` Option
|
|
123
|
+
|
|
124
|
+
`where` can be an object of column values or a SQL condition with parameters:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
expect(users).toHaveRow({ role: "admin" });
|
|
128
|
+
expect(users).toHaveRow({ sql: "name = ?", params: ["Ada"] });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
Install dependencies and run the checks, tests, or package build with Vite+:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
vp install
|
|
137
|
+
vp check
|
|
138
|
+
vp test
|
|
139
|
+
vp run build
|
|
140
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { i as db_adapter_d_exports, n as table_ref_d_exports, r as Type, t as Type$1 } from "./index-DZsTinBh.mjs";
|
|
2
|
+
import { Database } from "better-sqlite3";
|
|
3
|
+
declare namespace adapter_d_exports {
|
|
4
|
+
export { create };
|
|
5
|
+
}
|
|
6
|
+
declare function create(db: Database): Type;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/sqlite-better-sqlite-3/table-ref.d.ts
|
|
9
|
+
export declare function table(db: Type, name: string): Type$1;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { adapter_d_exports as BetterSqlite3Adapter };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { i as bindParse, n as buildWhere, r as quote } from "./matchers-8Jn4xSIt.mjs";
|
|
3
|
+
import { type } from "arktype";
|
|
4
|
+
//#region src/sqlite-better-sqlite-3/adapter/operates/table_exists.ts
|
|
5
|
+
const bindTableExists = (db) => (table) => {
|
|
6
|
+
return db.prepare("SELECT 1 FROM sqlite_master WHERE type IN ('table','view') AND name = ?").get(table) !== void 0;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/sqlite-better-sqlite-3/queries/get_table_foreign_keys.ts
|
|
10
|
+
const ForeignKeyInfoList = type({
|
|
11
|
+
id: "number",
|
|
12
|
+
seq: "number",
|
|
13
|
+
table: "string > 0",
|
|
14
|
+
from: "string > 0",
|
|
15
|
+
to: "string > 0",
|
|
16
|
+
on_update: "string > 0",
|
|
17
|
+
on_delete: "string > 0",
|
|
18
|
+
match: "string > 0"
|
|
19
|
+
}).array();
|
|
20
|
+
const parse = bindParse({
|
|
21
|
+
schema: ForeignKeyInfoList,
|
|
22
|
+
name: "ForeignKeyInfoList"
|
|
23
|
+
});
|
|
24
|
+
const getTableForeignKeys = (db) => (table) => {
|
|
25
|
+
const rows = db.prepare(`
|
|
26
|
+
SELECT key_lists.*
|
|
27
|
+
FROM sqlite_schema schema
|
|
28
|
+
JOIN pragma_foreign_key_list(schema.name) key_lists
|
|
29
|
+
WHERE schema.type = 'table'
|
|
30
|
+
AND schema.name = ?;
|
|
31
|
+
`).all(table);
|
|
32
|
+
return parse(rows);
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/sqlite-better-sqlite-3/adapter/operates/get_columns.ts
|
|
36
|
+
const bindGetColumns = (db) => (table) => {
|
|
37
|
+
const rows = db.prepare(`PRAGMA table_info(${quote(table)})`).all();
|
|
38
|
+
const fkInfoList = getTableForeignKeys(db)(table);
|
|
39
|
+
const uniqueInfoList = db.prepare(`
|
|
40
|
+
SELECT
|
|
41
|
+
iList.name AS index_name,
|
|
42
|
+
iList.origin,
|
|
43
|
+
iList.partial,
|
|
44
|
+
iInfo.seqno,
|
|
45
|
+
iInfo.name AS column_name
|
|
46
|
+
FROM pragma_index_list(?) AS iList
|
|
47
|
+
JOIN pragma_index_info(iList.name) AS iInfo
|
|
48
|
+
WHERE iList."unique" = 1
|
|
49
|
+
AND iList.partial = 0
|
|
50
|
+
ORDER BY iList.name, iInfo.seqno;
|
|
51
|
+
`).bind(table).all();
|
|
52
|
+
return rows.map((row) => {
|
|
53
|
+
const foreignKey = fkInfoList.find((fk) => fk.from === row.name);
|
|
54
|
+
const unique = uniqueInfoList.some((info) => info.column_name === row.name);
|
|
55
|
+
return {
|
|
56
|
+
name: row.name,
|
|
57
|
+
type: row.type,
|
|
58
|
+
notNull: row.notnull === 1,
|
|
59
|
+
primaryKey: row.pk > 0,
|
|
60
|
+
foreignKey: foreignKey ? {
|
|
61
|
+
table: foreignKey.table,
|
|
62
|
+
column: foreignKey.to
|
|
63
|
+
} : false,
|
|
64
|
+
unique
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/sqlite-better-sqlite-3/adapter/operates/count.ts
|
|
70
|
+
const bindCount = (db) => (table, where) => {
|
|
71
|
+
const { clause, params } = buildWhere(where);
|
|
72
|
+
return db.prepare(`SELECT COUNT(*) AS c FROM ${quote(table)}${clause}`).get(...params).c;
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/sqlite-better-sqlite-3/adapter/operates/find_rows.ts
|
|
76
|
+
const bindFindRows = (db) => (table, where) => {
|
|
77
|
+
const { clause, params } = buildWhere(where);
|
|
78
|
+
return db.prepare(`SELECT * FROM ${quote(table)}${clause}`).all(...params);
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/sqlite-better-sqlite-3/adapter/adapter.ts
|
|
82
|
+
var adapter_exports = /* @__PURE__ */ __exportAll({ create: () => create });
|
|
83
|
+
function create(db) {
|
|
84
|
+
return {
|
|
85
|
+
tableExists: bindTableExists(db),
|
|
86
|
+
getColumns: bindGetColumns(db),
|
|
87
|
+
count: bindCount(db),
|
|
88
|
+
findRows: bindFindRows(db)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/sqlite-better-sqlite-3/table-ref.ts
|
|
93
|
+
function table(db, name) {
|
|
94
|
+
return {
|
|
95
|
+
__dbTestingTable: true,
|
|
96
|
+
adapter: db,
|
|
97
|
+
name
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { adapter_exports as BetterSqlite3Adapter, table };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import "vitest";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
//#region src/arktype-helper/arktype-helpers.d.ts
|
|
4
|
+
type Parse<T> = (value: unknown) => T;
|
|
5
|
+
declare namespace column_info_d_exports {
|
|
6
|
+
export { ColumnInfo, Type$3 as Type, parse$2 as parse, schema$5 as schema };
|
|
7
|
+
}
|
|
8
|
+
type ColumnInfo = typeof schema$5.inferOut;
|
|
9
|
+
type Type$3 = ColumnInfo;
|
|
10
|
+
declare const schema$5: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
11
|
+
name: string;
|
|
12
|
+
type: string;
|
|
13
|
+
notNull: boolean;
|
|
14
|
+
primaryKey: boolean;
|
|
15
|
+
foreignKey: boolean | {
|
|
16
|
+
table: string;
|
|
17
|
+
column: string;
|
|
18
|
+
};
|
|
19
|
+
unique: boolean;
|
|
20
|
+
}, {}>;
|
|
21
|
+
declare const parse$2: Parse<{
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
notNull: boolean;
|
|
25
|
+
primaryKey: boolean;
|
|
26
|
+
foreignKey: boolean | {
|
|
27
|
+
table: string;
|
|
28
|
+
column: string;
|
|
29
|
+
};
|
|
30
|
+
unique: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
declare namespace where_d_exports {
|
|
33
|
+
export { Type$2 as Type, buildWhere, schema$4 as schema };
|
|
34
|
+
}
|
|
35
|
+
type Where = typeof Where.inferOut;
|
|
36
|
+
/** Row filter: an object of column = value (null means IS NULL), or a raw SQL WHERE clause. */
|
|
37
|
+
type Type$2 = Where;
|
|
38
|
+
declare const Where: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
39
|
+
[x: string]: string | number | null;
|
|
40
|
+
} | {
|
|
41
|
+
sql: string;
|
|
42
|
+
params?: unknown[] | undefined;
|
|
43
|
+
}, {}>;
|
|
44
|
+
declare const schema$4: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
45
|
+
[x: string]: string | number | null;
|
|
46
|
+
} | {
|
|
47
|
+
sql: string;
|
|
48
|
+
params?: unknown[] | undefined;
|
|
49
|
+
}, {}>;
|
|
50
|
+
declare function buildWhere(where?: Where): {
|
|
51
|
+
clause: string;
|
|
52
|
+
params: unknown[];
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/sqlite/domain/row.d.ts
|
|
56
|
+
type Row = typeof schema$3.inferOut;
|
|
57
|
+
declare const schema$3: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
58
|
+
[x: string]: string | number | null;
|
|
59
|
+
}, {}>;
|
|
60
|
+
declare namespace db_adapter_d_exports {
|
|
61
|
+
export { DbAdapter, Type$1 as Type, isDbAdapter, parse$1 as parse, schema$2 as schema };
|
|
62
|
+
}
|
|
63
|
+
type DbAdapter = typeof schema$2.inferOut;
|
|
64
|
+
type Type$1 = DbAdapter;
|
|
65
|
+
declare const schema$2: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
66
|
+
tableExists: (table: string) => boolean;
|
|
67
|
+
getColumns: (table: string) => Type$3[];
|
|
68
|
+
count: (table: string, where?: Type$2) => number;
|
|
69
|
+
findRows: (table: string, where?: Type$2) => Row[];
|
|
70
|
+
}, {}>;
|
|
71
|
+
declare function isDbAdapter(value: unknown): value is Type$1;
|
|
72
|
+
declare const parse$1: Parse<{
|
|
73
|
+
tableExists: (table: string) => boolean;
|
|
74
|
+
getColumns: (table: string) => Type$3[];
|
|
75
|
+
count: (table: string, where?: Type$2) => number;
|
|
76
|
+
findRows: (table: string, where?: Type$2) => Row[];
|
|
77
|
+
}>;
|
|
78
|
+
declare namespace table_ref_d_exports {
|
|
79
|
+
export { CreateTableRefFromDbAdapter, TableRef, Type, parse, schema$1 as schema };
|
|
80
|
+
}
|
|
81
|
+
/** Accepts a DbAdapter or a better-sqlite3 Database (e.g. drizzle's `db.$client`). */
|
|
82
|
+
type TableRef = typeof schema$1.inferOut;
|
|
83
|
+
type Type = TableRef;
|
|
84
|
+
declare const schema$1: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
85
|
+
__dbTestingTable: true;
|
|
86
|
+
adapter: {
|
|
87
|
+
tableExists: (table: string) => boolean;
|
|
88
|
+
getColumns: (table: string) => Type$3[];
|
|
89
|
+
count: (table: string, where?: Type$2) => number;
|
|
90
|
+
findRows: (table: string, where?: Type$2) => Row[];
|
|
91
|
+
};
|
|
92
|
+
name: string;
|
|
93
|
+
}, {}>;
|
|
94
|
+
declare const parse: Parse<{
|
|
95
|
+
__dbTestingTable: true;
|
|
96
|
+
adapter: {
|
|
97
|
+
tableExists: (table: string) => boolean;
|
|
98
|
+
getColumns: (table: string) => Type$3[];
|
|
99
|
+
count: (table: string, where?: Type$2) => number;
|
|
100
|
+
findRows: (table: string, where?: Type$2) => Row[];
|
|
101
|
+
};
|
|
102
|
+
name: string;
|
|
103
|
+
}>;
|
|
104
|
+
type CreateTableRefFromDbAdapter = (db: Type$1, name: string) => TableRef;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { Type$2 as a, db_adapter_d_exports as i, table_ref_d_exports as n, where_d_exports as o, Type$1 as r, Type as t };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
import { ErrorFactory } from "@praha/error-factory";
|
|
4
|
+
//#region src/arktype-helper/errors.ts
|
|
5
|
+
var SchemaParseError = class extends ErrorFactory({
|
|
6
|
+
name: "SchemaParseError",
|
|
7
|
+
message: ({ schema, value }) => `failed to parse value of ${schema}: ${JSON.stringify(value)}`,
|
|
8
|
+
fields: ErrorFactory.fields()
|
|
9
|
+
}) {};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/arktype-helper/arktype-helpers.ts
|
|
12
|
+
const bindParse = ({ schema, name }) => (value) => {
|
|
13
|
+
const parsed = schema(value);
|
|
14
|
+
if (parsed instanceof type.errors) throw new SchemaParseError({
|
|
15
|
+
schema: name,
|
|
16
|
+
value,
|
|
17
|
+
cause: parsed
|
|
18
|
+
});
|
|
19
|
+
return parsed;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/sqlite/domain/column-info.ts
|
|
23
|
+
const schema$4 = type({
|
|
24
|
+
name: "string > 0",
|
|
25
|
+
type: "string",
|
|
26
|
+
notNull: "boolean",
|
|
27
|
+
primaryKey: "boolean",
|
|
28
|
+
foreignKey: type({
|
|
29
|
+
table: "string > 0",
|
|
30
|
+
column: "string > 0"
|
|
31
|
+
}).or("boolean"),
|
|
32
|
+
unique: "boolean"
|
|
33
|
+
});
|
|
34
|
+
bindParse({
|
|
35
|
+
schema: schema$4,
|
|
36
|
+
name: "ColumnInfo"
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/sqlite/quote.ts
|
|
40
|
+
const quote = (id) => `"${id.replace(/"/g, "\"\"")}"`;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/sqlite/domain/row.ts
|
|
43
|
+
const RowValue = type("null | string | number");
|
|
44
|
+
bindParse({
|
|
45
|
+
schema: type({ "[string]": RowValue }),
|
|
46
|
+
name: "Row"
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/sqlite/domain/where.ts
|
|
50
|
+
const RowWhere = type({ "[string]": RowValue });
|
|
51
|
+
const SqlWhere = type({
|
|
52
|
+
sql: "string",
|
|
53
|
+
"params?": "unknown[]"
|
|
54
|
+
});
|
|
55
|
+
const schema$2 = type(RowWhere).or(SqlWhere);
|
|
56
|
+
function buildWhere(where) {
|
|
57
|
+
if (!where) return {
|
|
58
|
+
clause: "",
|
|
59
|
+
params: []
|
|
60
|
+
};
|
|
61
|
+
if (SqlWhere.allows(where)) return {
|
|
62
|
+
clause: ` WHERE (${where.sql})`,
|
|
63
|
+
params: where.params ?? []
|
|
64
|
+
};
|
|
65
|
+
const rowWhereList = Object.entries(where);
|
|
66
|
+
if (rowWhereList.length === 0) return {
|
|
67
|
+
clause: "",
|
|
68
|
+
params: []
|
|
69
|
+
};
|
|
70
|
+
const parts = [];
|
|
71
|
+
const params = [];
|
|
72
|
+
for (const [col, value] of rowWhereList) if (value === null) parts.push(`${quote(col)} IS NULL`);
|
|
73
|
+
else {
|
|
74
|
+
parts.push(`${quote(col)} = ?`);
|
|
75
|
+
params.push(typeof value === "boolean" ? Number(value) : value);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
clause: ` WHERE ${parts.join(" AND ")}`,
|
|
79
|
+
params
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/sqlite/domain/db-adapter.ts
|
|
84
|
+
const schema$1 = type({
|
|
85
|
+
tableExists: type("Function").pipe(type.fn("string", ":", "boolean")).as(),
|
|
86
|
+
getColumns: type("Function").pipe(type.fn("string", ":", schema$4.array())).as(),
|
|
87
|
+
count: type("Function").pipe(type.fn("string", schema$2.optional(), ":", "number")).as(),
|
|
88
|
+
findRows: type("Function").pipe(type.fn("string", schema$2.optional(), ":", "number")).as()
|
|
89
|
+
});
|
|
90
|
+
const parse$1 = bindParse({
|
|
91
|
+
schema: schema$1,
|
|
92
|
+
name: "DbAdapter"
|
|
93
|
+
});
|
|
94
|
+
const parse = bindParse({
|
|
95
|
+
schema: type({
|
|
96
|
+
__dbTestingTable: "true",
|
|
97
|
+
adapter: schema$1,
|
|
98
|
+
name: "string"
|
|
99
|
+
}),
|
|
100
|
+
name: "TableRef"
|
|
101
|
+
});
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/sqlite/matchers/db-adapter/to_have_table.ts
|
|
104
|
+
function toHaveTable(received, name) {
|
|
105
|
+
const pass = parse$1(received).tableExists(name);
|
|
106
|
+
return {
|
|
107
|
+
pass,
|
|
108
|
+
message: () => pass ? `expected table "${name}" not to exist` : `expected table "${name}" to exist`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/sqlite/matchers/_helper/show.ts
|
|
113
|
+
const show = (v) => JSON.stringify(v);
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/sqlite/matchers/table-ref/to_have_column.ts
|
|
116
|
+
function toHaveColumn(received, name, opts = {}) {
|
|
117
|
+
const tableRef = parse(received);
|
|
118
|
+
const actual = tableRef.adapter.getColumns(tableRef.name).find((c) => c.name === name);
|
|
119
|
+
let pass = actual !== void 0;
|
|
120
|
+
let mismatch;
|
|
121
|
+
if (actual) {
|
|
122
|
+
if (opts.type !== void 0 && actual.type !== opts.type) {
|
|
123
|
+
pass = false;
|
|
124
|
+
mismatch ??= `expected column "${tableRef.name}.${name}" to be of type ${opts.type}, but it is ${actual.type}`;
|
|
125
|
+
}
|
|
126
|
+
if (opts.notNull !== void 0 && actual.notNull !== opts.notNull) {
|
|
127
|
+
pass = false;
|
|
128
|
+
mismatch ??= `expected column "${tableRef.name}.${name}" to be ${opts.notNull ? "NOT NULL" : "nullable"}, but it is ${actual.notNull ? "NOT NULL" : "nullable"}`;
|
|
129
|
+
}
|
|
130
|
+
if (opts.foreignKey !== void 0) {
|
|
131
|
+
if (!(typeof opts.foreignKey === "boolean" ? !!actual.foreignKey === opts.foreignKey : typeof actual.foreignKey === "object" && actual.foreignKey.table === opts.foreignKey.table && actual.foreignKey.column === opts.foreignKey.column)) {
|
|
132
|
+
pass = false;
|
|
133
|
+
mismatch ??= `expected column "${tableRef.name}.${name}" to have foreign key ${show(opts.foreignKey)}, but it has ${show(actual.foreignKey)}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (opts.unique !== void 0 && actual.unique !== opts.unique) {
|
|
137
|
+
pass = false;
|
|
138
|
+
mismatch ??= `expected column "${tableRef.name}.${name}" to be ${opts.unique ? "unique" : "not unique"}, but it is ${actual.unique ? "unique" : "not unique"}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const type = opts.type ? ` of type ${opts.type}` : "";
|
|
142
|
+
return {
|
|
143
|
+
pass,
|
|
144
|
+
message: () => pass ? `expected table "${tableRef.name}" not to have column "${name}"${type}` : actual ? mismatch ?? `expected table "${tableRef.name}" to have column "${name}"${type}` : `expected table "${tableRef.name}" to have column "${name}"${type}`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/sqlite/matchers/table-ref/to_have_row.ts
|
|
149
|
+
function toHaveRow(received, where) {
|
|
150
|
+
const tableRef = parse(received);
|
|
151
|
+
const pass = tableRef.adapter.count(tableRef.name, where) > 0;
|
|
152
|
+
return {
|
|
153
|
+
pass,
|
|
154
|
+
message: () => pass ? `expected table "${tableRef.name}" not to have a row matching ${show(where)}` : `expected table "${tableRef.name}" to have a row matching ${show(where)}`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/sqlite/matchers/table-ref/to_have_row_count.ts
|
|
159
|
+
function toHaveRowCount(received, expected, where) {
|
|
160
|
+
const tableRef = parse(received);
|
|
161
|
+
const actual = where === void 0 ? tableRef.adapter.count(tableRef.name) : tableRef.adapter.count(tableRef.name, where);
|
|
162
|
+
const pass = actual === expected;
|
|
163
|
+
return {
|
|
164
|
+
pass,
|
|
165
|
+
message: () => pass ? `expected table "${tableRef.name}" not to have ${expected} rows matching ${show(where)}` : `expected table "${tableRef.name}" to have ${expected} rows matching ${show(where)}, but it has ${actual}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/sqlite/matchers/table-ref/to_be_empty_table.ts
|
|
170
|
+
function toBeEmptyTable(received) {
|
|
171
|
+
const tableRef = parse(received);
|
|
172
|
+
const actual = tableRef.adapter.count(tableRef.name);
|
|
173
|
+
const pass = actual === 0;
|
|
174
|
+
return {
|
|
175
|
+
pass,
|
|
176
|
+
message: () => pass ? `expected table "${tableRef.name}" not to be empty` : `expected table "${tableRef.name}" to be empty, but it has ${actual} rows`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/sqlite/matchers/index.ts
|
|
181
|
+
var matchers_exports = /* @__PURE__ */ __exportAll({
|
|
182
|
+
toBeEmptyTable: () => toBeEmptyTable,
|
|
183
|
+
toHaveColumn: () => toHaveColumn,
|
|
184
|
+
toHaveRow: () => toHaveRow,
|
|
185
|
+
toHaveRowCount: () => toHaveRowCount,
|
|
186
|
+
toHaveTable: () => toHaveTable
|
|
187
|
+
});
|
|
188
|
+
//#endregion
|
|
189
|
+
export { bindParse as i, buildWhere as n, quote as r, matchers_exports as t };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { n as TableRefMatchers, t as DbAdapterMatchers } from "./vitest-DjF_zZFj.mjs";
|
|
2
|
+
import "vite-plus/test";
|
|
3
|
+
//#region src/vite-plus.d.ts
|
|
4
|
+
declare module "vite-plus/test" {
|
|
5
|
+
interface Matchers<R, T> extends DbAdapterMatchers<R, T>, TableRefMatchers<R, T> {}
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { a as Type$3, i as db_adapter_d_exports, n as table_ref_d_exports, o as where_d_exports, r as Type$1, t as Type$2 } from "./index-DZsTinBh.mjs";
|
|
2
|
+
declare namespace sqlite_column_type_d_exports {
|
|
3
|
+
export { Type };
|
|
4
|
+
}
|
|
5
|
+
declare const all: readonly ["TEXT", "INTEGER", "REAL", "BLOB"];
|
|
6
|
+
type Type = (typeof all)[number];
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/vitest.d.ts
|
|
9
|
+
interface DbAdapterMatchers<R, T> {
|
|
10
|
+
/** expect(db).toHaveTable('users') */
|
|
11
|
+
toHaveTable: T extends Type$1 ? (name: string) => R : never;
|
|
12
|
+
}
|
|
13
|
+
interface TableRefMatchers<R, T> {
|
|
14
|
+
/** expect(table(db, 'users')).toHaveColumn('name', { type: 'TEXT' }) */
|
|
15
|
+
toHaveColumn: T extends Type$2 ? (name: string, opts?: {
|
|
16
|
+
type?: Type;
|
|
17
|
+
notNull?: boolean;
|
|
18
|
+
foreignKey?: boolean | {
|
|
19
|
+
table: string;
|
|
20
|
+
column: string;
|
|
21
|
+
};
|
|
22
|
+
unique?: boolean;
|
|
23
|
+
}) => R : never;
|
|
24
|
+
/** expect(table(db, 'users')).toHaveRow({ role: 'admin' }) or ({ sql: 'age > ?', params: [20] }) */
|
|
25
|
+
toHaveRow: T extends Type$2 ? (where: Type$3) => R : never;
|
|
26
|
+
toHaveRowCount: T extends Type$2 ? (expected: number, where?: Type$3) => R : never;
|
|
27
|
+
toBeEmptyTable: T extends Type$2 ? () => R : never;
|
|
28
|
+
}
|
|
29
|
+
declare module "vitest" {
|
|
30
|
+
interface Matchers<R, T> extends DbAdapterMatchers<R, T>, TableRefMatchers<R, T> {}
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { TableRefMatchers as n, DbAdapterMatchers as t };
|
package/dist/vitest.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mitsuru793/vitest-sqlite",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
|
+
"description": "Vitest matchers and adapters for testing SQLite databases, with better-sqlite3 support.",
|
|
5
|
+
"homepage": "https://github.com/mitsuru793/vitest-sqlite#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/mitsuru793/vitest-sqlite/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "mitsuru793 <mitsuru793@gmail.com>",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/mitsuru793/vitest-sqlite.git"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
"./better-sqlite3": "./dist/better-sqlite3.mjs",
|
|
21
|
+
"./vite-plus": "./dist/vite-plus.mjs",
|
|
22
|
+
"./vitest": "./dist/vitest.mjs",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@praha/error-factory": "^1.4.1",
|
|
30
|
+
"arktype": "^2.2.8",
|
|
31
|
+
"better-sqlite3": "^12.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
35
|
+
"@types/node": "^26.6.5",
|
|
36
|
+
"bumpp": "^12.3.0",
|
|
37
|
+
"typescript": "^7.0.2",
|
|
38
|
+
"vite-plus": "1.1.0",
|
|
39
|
+
"vitest": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"vitest": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devEngines": {
|
|
45
|
+
"packageManager": {
|
|
46
|
+
"name": "pnpm",
|
|
47
|
+
"version": "12.14.0",
|
|
48
|
+
"onFail": "download"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "vp pack",
|
|
53
|
+
"dev": "vp pack --watch",
|
|
54
|
+
"test": "vp test",
|
|
55
|
+
"check": "vp check"
|
|
56
|
+
}
|
|
6
57
|
}
|