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 CHANGED
@@ -3,11 +3,11 @@
3
3
  [![License: MIT](https://img.shields.io/npm/l/expo-sqlite-mock.svg)](https://github.com/zfben/expo-sqlite-mock/blob/main/packages/faasjs/jest/LICENSE)
4
4
  [![NPM Version](https://img.shields.io/npm/v/expo-sqlite-mock.svg)](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 ~52.
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.1",
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 --detectOpenHandles --testTimeout=10000"
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": "~52"
29
+ "expo": ">=52"
29
30
  },
30
31
  "devDependencies": {
31
32
  "typescript": "*",
32
- "@biomejs/biome": "*",
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.3.1",
39
- "@faasjs/jest": "*",
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
  }
@@ -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: jest
22
- .fn()
23
- .mockImplementation(
24
- (databaseName: string, options?: SQLiteOpenOptions, serializedData?: Uint8Array) =>
25
- new NativeDatabase(databaseName, options, serializedData)
26
- ),
20
+ NativeDatabase: (
21
+ databaseName: string,
22
+ options?: SQLiteOpenOptions,
23
+ serializedData?: Uint8Array
24
+ ) => new NativeDatabase(databaseName, options, serializedData),
27
25
 
28
- NativeStatement: jest.fn().mockImplementation(() => new NativeStatement()),
26
+ NativeStatement: () => new NativeStatement(),
29
27
 
30
28
  defaultDatabaseDirectory: '.',
31
29
 
32
- ensureDatabasePathExistsAsync: jest.fn().mockImplementation(async (databasePath: string) => {}),
33
- ensureDatabasePathExistsSync: jest.fn().mockImplementation((databasePath: string) => {}),
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(databaseName: string, options?: SQLiteOpenOptions, serializedData?: Uint8Array) {
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 = jest.fn().mockResolvedValue(null)
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) => this.sqlite3Db.serialize({ attached: databaseName })
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 = jest.fn()
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) => this.sqlite3Db.serialize({ attached: databaseName })
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 = jest
89
- .fn()
90
- .mockImplementation(
91
- (
92
- database: NativeDatabase,
93
- bindParams: SQLiteBindPrimitiveParams,
94
- bindBlobParams: SQLiteBindBlobParams,
95
- shouldPassAsArray: boolean
96
- ): Promise<SQLiteRunResult & { firstRowValues: SQLiteColumnValues }> =>
97
- Promise.resolve(this._run(normalizeSQLite3Args(bindParams, bindBlobParams, shouldPassAsArray)))
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 = jest.fn().mockImplementation((database: NativeDatabase): Promise<any> => {
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 = result.done === false ? Object.values(result.value as Record<string, any>) : null
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 = (database: NativeDatabase) => Promise.resolve(this._allValues())
111
- public getColumnNamesAsync = async (database: NativeDatabase) => {
112
- assert(this.sqlite3Stmt)
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 (database: NativeDatabase) => {
122
+ public resetAsync = async (_database: NativeDatabase) => {
116
123
  this._reset()
117
124
  }
118
- public finalizeAsync = async (database: NativeDatabase) => {
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 = jest
127
- .fn()
128
- .mockImplementation(
129
- (
130
- database: NativeDatabase,
131
- bindParams: SQLiteBindPrimitiveParams,
132
- bindBlobParams: SQLiteBindBlobParams,
133
- shouldPassAsArray: boolean
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
- public stepSync = (database: NativeDatabase): any => {
138
- assert(this.sqlite3Stmt)
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 = result.done === false ? Object.values(result.value as Record<string, any>) : null
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 = (database: NativeDatabase) => this._allValues()
150
- public getColumnNamesSync = (database: NativeDatabase) => {
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 = (database: NativeDatabase) => {
161
+ public resetSync = (_database: NativeDatabase) => {
155
162
  this._reset()
156
163
  }
157
- public finalizeSync = (database: NativeDatabase) => {
164
+ public finalizeSync = (_database: NativeDatabase) => {
158
165
  this._finalize()
159
166
  }
160
167
 
161
168
  //#endregion
162
169
 
163
- private _run = (...params: any[]): SQLiteRunResult & { firstRowValues: SQLiteColumnValues } => {
164
- assert(this.sqlite3Stmt)
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
- assert(this.sqlite3Stmt)
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
  }