expo-sqlite-mock 2.0.1 → 2.2.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/README.md +45 -2
- package/package.json +13 -9
- package/src/ExpoSQLiteNext.ts +79 -58
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
[](https://github.com/zfben/expo-sqlite-mock/blob/main/packages/faasjs/jest/LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/expo-sqlite-mock)
|
|
5
5
|
|
|
6
|
-
Use expo-sqlite with jest.
|
|
6
|
+
Use [expo-sqlite](https://docs.expo.dev/versions/latest/sdk/sqlite/) with jest.
|
|
7
7
|
|
|
8
8
|
## Notice
|
|
9
9
|
|
|
10
|
-
- **~2.0.0** is for expo-sqlite
|
|
10
|
+
- **~2.0.0** is for expo-sqlite >=52.
|
|
11
11
|
- **~1.0.0** is for expo-sqlite ~51.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
@@ -15,8 +15,51 @@ Use expo-sqlite with jest.
|
|
|
15
15
|
1. `npm install -D expo-sqlite-mock` or `bun add -D expo-sqlite-mock`
|
|
16
16
|
2. Add `"setupFilesAfterEnv": ["expo-sqlite-mock/src/setup.ts"]` and `"testTimeout": 10000` to your jest config (It's in `package.json` for default expo project).
|
|
17
17
|
|
|
18
|
+
Example:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"jest": {
|
|
23
|
+
"preset": "jest-expo",
|
|
24
|
+
"setupFilesAfterEnv": ["expo-sqlite-mock/src/setup.ts"],
|
|
25
|
+
"testTimeout": 10000
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Advanced
|
|
31
|
+
|
|
32
|
+
You can set the `EXPO_SQLITE_MOCK` environment variable to a custom SQLite database location.
|
|
33
|
+
|
|
34
|
+
Tips:
|
|
35
|
+
|
|
36
|
+
1. Please use `JEST_WORKER_ID` to avoid concurrent test cases writing to the same file.
|
|
37
|
+
2. Update your `.gitignore` to ignore the SQLite database file.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
it("test", async () => {
|
|
43
|
+
// or you can set it beforeAll or beforeEach
|
|
44
|
+
process.env.EXPO_SQLITE_MOCK = `${__dirname}/test_${process.env.JEST_WORKER_ID}.db`;
|
|
45
|
+
|
|
46
|
+
// your test code
|
|
47
|
+
|
|
48
|
+
// clear the env var
|
|
49
|
+
delete process.env.EXPO_SQLITE_MOCK;
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
18
53
|
## Changelog
|
|
19
54
|
|
|
55
|
+
- **2.2.0**
|
|
56
|
+
- Compatible with drizzle
|
|
57
|
+
- Clean up the code
|
|
58
|
+
|
|
59
|
+
- **2.1.0**
|
|
60
|
+
- Support custom SQLite database location by setting the `EXPO_SQLITE_MOCK` environment variable.
|
|
61
|
+
- Update peer dependencies.
|
|
62
|
+
|
|
20
63
|
- **2.0.1**
|
|
21
64
|
- Fix setup.
|
|
22
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-sqlite-mock",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://github.com/zfben/expo-sqlite-mock",
|
|
6
6
|
"repository": {
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
"url": "https://github.com/zfben/expo-sqlite-mock/issues"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "jest --coverage --forceExit --
|
|
14
|
+
"test": "jest --coverage --forceExit --testTimeout=10000"
|
|
15
15
|
},
|
|
16
16
|
"jest": {
|
|
17
17
|
"preset": "jest-expo",
|
|
18
|
+
"testEnvironment": "node",
|
|
18
19
|
"testRegex": "/*\\.test\\.tsx?$"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
@@ -25,19 +26,22 @@
|
|
|
25
26
|
"better-sqlite3": "*",
|
|
26
27
|
"jest": "*",
|
|
27
28
|
"expo-sqlite": "*",
|
|
28
|
-
"expo": "
|
|
29
|
+
"expo": ">=52"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"typescript": "*",
|
|
32
|
-
"@
|
|
33
|
+
"@faasjs/lint": "*",
|
|
33
34
|
"expo-sqlite": "*",
|
|
34
35
|
"better-sqlite3": "*",
|
|
35
36
|
"@types/better-sqlite3": "*",
|
|
36
|
-
"@types/react": "
|
|
37
|
-
"react": "
|
|
38
|
-
"react-test-renderer": "18
|
|
39
|
-
"
|
|
37
|
+
"@types/react": "^18",
|
|
38
|
+
"react": "^18",
|
|
39
|
+
"react-test-renderer": "^18",
|
|
40
|
+
"react-native": "~0.77",
|
|
40
41
|
"@testing-library/react-native": "*",
|
|
41
|
-
"jest-expo": "~52"
|
|
42
|
+
"jest-expo": "~52",
|
|
43
|
+
"@types/jest": "*",
|
|
44
|
+
"jest": "*",
|
|
45
|
+
"drizzle-orm": "*"
|
|
42
46
|
}
|
|
43
47
|
}
|
package/src/ExpoSQLiteNext.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
1
|
import sqlite3 from 'better-sqlite3'
|
|
3
2
|
|
|
4
3
|
import type { SQLiteOpenOptions } from 'expo-sqlite/src/NativeDatabase'
|
|
@@ -18,19 +17,18 @@ export const mockedExpoSqliteNext = {
|
|
|
18
17
|
for (const db of dbs) await db.closeAsync()
|
|
19
18
|
},
|
|
20
19
|
|
|
21
|
-
NativeDatabase:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
),
|
|
20
|
+
NativeDatabase: (
|
|
21
|
+
databaseName: string,
|
|
22
|
+
options?: SQLiteOpenOptions,
|
|
23
|
+
serializedData?: Uint8Array
|
|
24
|
+
) => new NativeDatabase(databaseName, options, serializedData),
|
|
27
25
|
|
|
28
|
-
NativeStatement:
|
|
26
|
+
NativeStatement: () => new NativeStatement(),
|
|
29
27
|
|
|
30
28
|
defaultDatabaseDirectory: '.',
|
|
31
29
|
|
|
32
|
-
ensureDatabasePathExistsAsync:
|
|
33
|
-
ensureDatabasePathExistsSync:
|
|
30
|
+
ensureDatabasePathExistsAsync: async (_databasePath: string) => {},
|
|
31
|
+
ensureDatabasePathExistsSync: (_databasePath: string) => {},
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
//#region async sqlite3
|
|
@@ -41,22 +39,27 @@ export const mockedExpoSqliteNext = {
|
|
|
41
39
|
class NativeDatabase {
|
|
42
40
|
private readonly sqlite3Db: sqlite3.Database
|
|
43
41
|
|
|
44
|
-
constructor(
|
|
42
|
+
constructor(
|
|
43
|
+
_databaseName: string,
|
|
44
|
+
_options?: SQLiteOpenOptions,
|
|
45
|
+
serializedData?: Uint8Array
|
|
46
|
+
) {
|
|
45
47
|
if (serializedData != null) {
|
|
46
48
|
this.sqlite3Db = new sqlite3(Buffer.from(serializedData))
|
|
47
49
|
} else {
|
|
48
|
-
this.sqlite3Db = new sqlite3(':memory:')
|
|
50
|
+
this.sqlite3Db = new sqlite3(process.env.EXPO_SQLITE_MOCK || ':memory:')
|
|
49
51
|
}
|
|
50
52
|
dbs.push(this)
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
//#region Asynchronous API
|
|
54
56
|
|
|
55
|
-
initAsync =
|
|
57
|
+
initAsync = () => Promise.resolve()
|
|
56
58
|
isInTransactionAsync = async () => this.sqlite3Db.inTransaction
|
|
57
59
|
closeAsync = async () => this.sqlite3Db.close()
|
|
58
60
|
execAsync = async (source: string) => this.sqlite3Db.exec(source)
|
|
59
|
-
serializeAsync = async (databaseName: string) =>
|
|
61
|
+
serializeAsync = async (databaseName: string) =>
|
|
62
|
+
this.sqlite3Db.serialize({ attached: databaseName })
|
|
60
63
|
prepareAsync = async (nativeStatement: NativeStatement, source: string) => {
|
|
61
64
|
nativeStatement.sqlite3Stmt = this.sqlite3Db.prepare(source)
|
|
62
65
|
}
|
|
@@ -65,11 +68,12 @@ class NativeDatabase {
|
|
|
65
68
|
|
|
66
69
|
//#region Synchronous API
|
|
67
70
|
|
|
68
|
-
initSync =
|
|
71
|
+
initSync = () => {}
|
|
69
72
|
isInTransactionSync = () => this.sqlite3Db.inTransaction
|
|
70
73
|
closeSync = () => this.sqlite3Db.close()
|
|
71
74
|
execSync = (source: string) => this.sqlite3Db.exec(source)
|
|
72
|
-
serializeSync = (databaseName: string) =>
|
|
75
|
+
serializeSync = (databaseName: string) =>
|
|
76
|
+
this.sqlite3Db.serialize({ attached: databaseName })
|
|
73
77
|
prepareSync = (nativeStatement: NativeStatement, source: string) => {
|
|
74
78
|
nativeStatement.sqlite3Stmt = this.sqlite3Db.prepare(source)
|
|
75
79
|
}
|
|
@@ -82,40 +86,43 @@ class NativeDatabase {
|
|
|
82
86
|
class NativeStatement {
|
|
83
87
|
public sqlite3Stmt: sqlite3.Statement | null = null
|
|
84
88
|
private iterator: ReturnType<sqlite3.Statement['iterate']> | null = null
|
|
89
|
+
private cachedRows: any[] = []
|
|
85
90
|
|
|
86
91
|
//#region Asynchronous API
|
|
87
92
|
|
|
88
|
-
public runAsync =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
public runAsync = (
|
|
94
|
+
_database: NativeDatabase,
|
|
95
|
+
bindParams: SQLiteBindPrimitiveParams,
|
|
96
|
+
bindBlobParams: SQLiteBindBlobParams,
|
|
97
|
+
shouldPassAsArray: boolean
|
|
98
|
+
): Promise<SQLiteRunResult & { firstRowValues: SQLiteColumnValues }> =>
|
|
99
|
+
Promise.resolve(
|
|
100
|
+
this._run(
|
|
101
|
+
normalizeSQLite3Args(bindParams, bindBlobParams, shouldPassAsArray)
|
|
102
|
+
)
|
|
98
103
|
)
|
|
99
|
-
public stepAsync =
|
|
100
|
-
assert(this.sqlite3Stmt)
|
|
104
|
+
public stepAsync = (_database: NativeDatabase): Promise<any> => {
|
|
101
105
|
if (this.iterator == null) {
|
|
102
106
|
this.iterator = this.sqlite3Stmt.iterate()
|
|
103
107
|
// Since the first row is retrieved by `_run()`, we need to skip the first row here.
|
|
104
108
|
this.iterator.next()
|
|
105
109
|
}
|
|
106
110
|
const result = this.iterator.next()
|
|
107
|
-
const columnValues =
|
|
111
|
+
const columnValues =
|
|
112
|
+
result.done === false
|
|
113
|
+
? Object.values(result.value as Record<string, any>)
|
|
114
|
+
: null
|
|
108
115
|
return Promise.resolve(columnValues)
|
|
109
|
-
}
|
|
110
|
-
public getAllAsync = (
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
}
|
|
117
|
+
public getAllAsync = (_database: NativeDatabase) =>
|
|
118
|
+
Promise.resolve(this._allValues())
|
|
119
|
+
public getColumnNamesAsync = async (_database: NativeDatabase) => {
|
|
113
120
|
return this.sqlite3Stmt.columns().map(column => column.name)
|
|
114
121
|
}
|
|
115
|
-
public resetAsync = async (
|
|
122
|
+
public resetAsync = async (_database: NativeDatabase) => {
|
|
116
123
|
this._reset()
|
|
117
124
|
}
|
|
118
|
-
public finalizeAsync = async (
|
|
125
|
+
public finalizeAsync = async (_database: NativeDatabase) => {
|
|
119
126
|
this._finalize()
|
|
120
127
|
}
|
|
121
128
|
|
|
@@ -123,19 +130,17 @@ class NativeStatement {
|
|
|
123
130
|
|
|
124
131
|
//#region Synchronous API
|
|
125
132
|
|
|
126
|
-
public runSync =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
): SQLiteRunResult & { firstRowValues: SQLiteColumnValues } =>
|
|
135
|
-
this._run(normalizeSQLite3Args(bindParams, bindBlobParams, shouldPassAsArray))
|
|
133
|
+
public runSync = (
|
|
134
|
+
_database: NativeDatabase,
|
|
135
|
+
bindParams: SQLiteBindPrimitiveParams,
|
|
136
|
+
bindBlobParams: SQLiteBindBlobParams,
|
|
137
|
+
shouldPassAsArray: boolean
|
|
138
|
+
): SQLiteRunResult & { firstRowValues: SQLiteColumnValues } =>
|
|
139
|
+
this._run(
|
|
140
|
+
normalizeSQLite3Args(bindParams, bindBlobParams, shouldPassAsArray)
|
|
136
141
|
)
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
|
|
143
|
+
public stepSync = (_database: NativeDatabase): any => {
|
|
139
144
|
if (this.iterator == null) {
|
|
140
145
|
this.iterator = this.sqlite3Stmt.iterate()
|
|
141
146
|
// Since the first row is retrieved by `_run()`, we need to skip the first row here.
|
|
@@ -143,33 +148,48 @@ class NativeStatement {
|
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
const result = this.iterator.next()
|
|
146
|
-
const columnValues =
|
|
151
|
+
const columnValues =
|
|
152
|
+
result.done === false
|
|
153
|
+
? Object.values(result.value as Record<string, any>)
|
|
154
|
+
: null
|
|
147
155
|
return columnValues
|
|
148
156
|
}
|
|
149
|
-
public getAllSync = (
|
|
150
|
-
public getColumnNamesSync = (
|
|
151
|
-
assert(this.sqlite3Stmt)
|
|
157
|
+
public getAllSync = (_database: NativeDatabase) => this._allValues()
|
|
158
|
+
public getColumnNamesSync = (_database: NativeDatabase) => {
|
|
152
159
|
return this.sqlite3Stmt.columns().map(column => column.name)
|
|
153
160
|
}
|
|
154
|
-
public resetSync = (
|
|
161
|
+
public resetSync = (_database: NativeDatabase) => {
|
|
155
162
|
this._reset()
|
|
156
163
|
}
|
|
157
|
-
public finalizeSync = (
|
|
164
|
+
public finalizeSync = (_database: NativeDatabase) => {
|
|
158
165
|
this._finalize()
|
|
159
166
|
}
|
|
160
167
|
|
|
161
168
|
//#endregion
|
|
162
169
|
|
|
163
|
-
private _run = (
|
|
164
|
-
|
|
170
|
+
private _run = (
|
|
171
|
+
...params: any[]
|
|
172
|
+
): SQLiteRunResult & { firstRowValues: SQLiteColumnValues } => {
|
|
165
173
|
this.sqlite3Stmt.bind(...params)
|
|
174
|
+
|
|
175
|
+
// Avoid insert with returning being executed multiple times
|
|
176
|
+
if (this.sqlite3Stmt.source.includes(' returning ')) {
|
|
177
|
+
this.cachedRows = this.sqlite3Stmt.all()
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
lastInsertRowId: this.cachedRows[0].id,
|
|
181
|
+
changes: params.length,
|
|
182
|
+
firstRowValues: Object.values(this.cachedRows[0]),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
166
186
|
const result = this.sqlite3Stmt.run()
|
|
167
187
|
|
|
168
188
|
// better-sqlite3 does not support run() returning the first row, use get() instead.
|
|
169
189
|
let firstRow: any
|
|
170
190
|
try {
|
|
171
191
|
firstRow = this.sqlite3Stmt.get()
|
|
172
|
-
} catch {
|
|
192
|
+
} catch (_) {
|
|
173
193
|
// better-sqlite3 may throw `TypeError: This statement does not return data. Use run() instead`
|
|
174
194
|
firstRow = null
|
|
175
195
|
}
|
|
@@ -181,7 +201,9 @@ class NativeStatement {
|
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
private _allValues = (): SQLiteColumnNames[] => {
|
|
184
|
-
|
|
204
|
+
if (this.sqlite3Stmt.source.includes(' returning '))
|
|
205
|
+
return this.cachedRows.slice(1).map(row => Object.values(row))
|
|
206
|
+
|
|
185
207
|
const sqlite3Stmt = this.sqlite3Stmt as any
|
|
186
208
|
// Since the first row is retrieved by `_run()`, we need to skip the first row here.
|
|
187
209
|
return sqlite3Stmt
|
|
@@ -191,7 +213,6 @@ class NativeStatement {
|
|
|
191
213
|
}
|
|
192
214
|
|
|
193
215
|
private _reset = () => {
|
|
194
|
-
assert(this.sqlite3Stmt)
|
|
195
216
|
this.iterator?.return?.()
|
|
196
217
|
this.iterator = this.sqlite3Stmt.iterate()
|
|
197
218
|
}
|