diff --git a/packages/attachments/package.json b/packages/attachments/package.json index e633726e..b72d83b2 100644 --- a/packages/attachments/package.json +++ b/packages/attachments/package.json @@ -25,9 +25,24 @@ "build": "tsc -b", "build:prod": "tsc -b --sourceMap false", "clean": "rm -rf lib tsconfig.tsbuildinfo", - "watch": "tsc -b -w" + "watch": "tsc -b -w", + "test": "pnpm build && vitest" }, "peerDependencies": { "@powersync/common": "workspace:^1.18.1" + }, + "devDependencies": { + "@powersync/web": "workspace:*", + "@journeyapps/wa-sqlite": "^1.0.0", + "@types/node": "^20.17.6", + "@vitest/browser": "^2.1.4", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3", + "vite": "^5.4.10", + "vite-plugin-top-level-await": "^1.4.4", + "vite-plugin-wasm": "^3.3.0", + "vitest": "^2.1.4", + "webdriverio": "^9.2.8" } } diff --git a/packages/attachments/src/AbstractAttachmentQueue.ts b/packages/attachments/src/AbstractAttachmentQueue.ts index 806ee142..8e5dd658 100644 --- a/packages/attachments/src/AbstractAttachmentQueue.ts +++ b/packages/attachments/src/AbstractAttachmentQueue.ts @@ -300,6 +300,9 @@ export abstract class AbstractAttachmentQueue {}), + resolveTables: vi.fn(() => ['table1', 'table2']), + onChangeWithCallback: vi.fn(), + getAll: vi.fn(() => Promise.resolve([{id: 'test-1'}, {id: 'test-2'}])), + execute: vi.fn(() => Promise.resolve()), + getOptional: vi.fn((_query, params) => Promise.resolve(record)), + watch: vi.fn((query, params, callbacks) => { + callbacks?.onResult?.({ rows: { _array: [{id: 'test-1'}, {id: 'test-2'}] } }); + }), + writeTransaction: vi.fn(async (callback) => { + await callback({ + execute: vi.fn(() => Promise.resolve()) + }); + }) +}; + +const mockStorage: StorageAdapter = { + downloadFile: vi.fn(), + uploadFile: vi.fn(), + deleteFile: vi.fn(), + writeFile: vi.fn(), + readFile: vi.fn(), + fileExists: vi.fn(), + makeDir: vi.fn(), + copyFile: vi.fn(), + getUserStorageDirectory: vi.fn() +}; + +class TestAttachmentQueue extends AbstractAttachmentQueue { + onAttachmentIdsChange(onUpdate: (ids: string[]) => void): void { + throw new Error('Method not implemented.'); + } + newAttachmentRecord(record?: Partial): Promise { + throw new Error('Method not implemented.'); + } +} + +describe('attachments', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should not download attachments when downloadRecord is called with downloadAttachments false', async () => { + const queue = new TestAttachmentQueue({ + powersync: mockPowerSync as any, + storage: mockStorage, + downloadAttachments: false + }); + + await queue.downloadRecord(record); + + expect(mockStorage.downloadFile).not.toHaveBeenCalled(); + }); + + it('should download attachments when downloadRecord is called with downloadAttachments true', async () => { + const queue = new TestAttachmentQueue({ + powersync: mockPowerSync as any, + storage: mockStorage, + downloadAttachments: true + }); + + await queue.downloadRecord(record); + + expect(mockStorage.downloadFile).toHaveBeenCalled(); + }); + + // Testing the inverse of this test, i.e. when downloadAttachments is false, is not required as you can't wait for something that does not happen + it('should not download attachments with watchDownloads is called with downloadAttachments false', async () => { + const queue = new TestAttachmentQueue({ + powersync: mockPowerSync as any, + storage: mockStorage, + downloadAttachments: true + }); + + queue.watchDownloads(); + await vi.waitFor(() => { + expect(mockStorage.downloadFile).toBeCalledTimes(2); + }); + }); +}); diff --git a/packages/attachments/vitest.config.ts b/packages/attachments/vitest.config.ts new file mode 100644 index 00000000..cbbefe1c --- /dev/null +++ b/packages/attachments/vitest.config.ts @@ -0,0 +1,29 @@ +import wasm from 'vite-plugin-wasm'; +import topLevelAwait from 'vite-plugin-top-level-await'; +import { defineConfig, UserConfigExport } from 'vitest/config'; + +const config: UserConfigExport = { + worker: { + format: 'es', + plugins: () => [wasm(), topLevelAwait()] + }, + optimizeDeps: { + // Don't optimise these packages as they contain web workers and WASM files. + // https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673 + exclude: ['@journeyapps/wa-sqlite', '@powersync/web'] + }, + plugins: [wasm(), topLevelAwait()], + test: { + isolate: false, + globals: true, + include: ['tests/**/*.test.ts'], + browser: { + enabled: true, + headless: true, + provider: 'webdriverio', + name: 'chrome' // browser name is required + } + } +}; + +export default defineConfig(config); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8853909..b7247603 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,16 +234,16 @@ importers: dependencies: '@capacitor/android': specifier: ^6.0.0 - version: 6.1.2(@capacitor/core@6.1.2) + version: 6.1.2(@capacitor/core@6.2.0) '@capacitor/core': specifier: latest - version: 6.1.2 + version: 6.2.0 '@capacitor/ios': specifier: ^6.0.0 - version: 6.1.2(@capacitor/core@6.1.2) + version: 6.1.2(@capacitor/core@6.2.0) '@capacitor/splash-screen': specifier: latest - version: 6.0.2(@capacitor/core@6.1.2) + version: 6.0.3(@capacitor/core@6.2.0) '@journeyapps/wa-sqlite': specifier: ^1.0.0 version: 1.0.0 @@ -1527,6 +1527,43 @@ importers: '@powersync/common': specifier: workspace:^1.18.1 version: link:../common + devDependencies: + '@journeyapps/wa-sqlite': + specifier: ^1.0.0 + version: 1.0.0 + '@powersync/web': + specifier: workspace:* + version: link:../web + '@types/node': + specifier: ^20.17.6 + version: 20.17.6 + '@vitest/browser': + specifier: ^2.1.4 + version: 2.1.4(@types/node@20.17.6)(typescript@5.6.3)(vite@5.4.11(@types/node@20.17.6)(less@4.2.0)(sass@1.79.4)(terser@5.34.1))(vitest@2.1.4)(webdriverio@9.2.12) + ts-loader: + specifier: ^9.5.1 + version: 9.5.1(typescript@5.6.3)(webpack@5.95.0(@swc/core@1.7.26(@swc/helpers@0.5.5))) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.5))(@types/node@20.17.6)(typescript@5.6.3) + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vite: + specifier: ^5.4.10 + version: 5.4.11(@types/node@20.17.6)(less@4.2.0)(sass@1.79.4)(terser@5.34.1) + vite-plugin-top-level-await: + specifier: ^1.4.4 + version: 1.4.4(@swc/helpers@0.5.5)(rollup@4.24.0)(vite@5.4.11(@types/node@20.17.6)(less@4.2.0)(sass@1.79.4)(terser@5.34.1)) + vite-plugin-wasm: + specifier: ^3.3.0 + version: 3.3.0(vite@5.4.11(@types/node@20.17.6)(less@4.2.0)(sass@1.79.4)(terser@5.34.1)) + vitest: + specifier: ^2.1.4 + version: 2.1.4(@types/node@20.17.6)(@vitest/browser@2.1.4)(jsdom@24.1.3)(less@4.2.0)(msw@2.6.4(@types/node@20.17.6)(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + webdriverio: + specifier: ^9.2.8 + version: 9.2.12 packages/common: dependencies: @@ -3199,16 +3236,16 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@capacitor/core@6.1.2': - resolution: {integrity: sha512-xFy1/4qLFLp5WCIzIhtwUuVNNoz36+V7/BzHmLqgVJcvotc4MMjswW/TshnPQaLLujEOaLkA4h8ZJ0uoK3ImGg==} + '@capacitor/core@6.2.0': + resolution: {integrity: sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==} '@capacitor/ios@6.1.2': resolution: {integrity: sha512-HaeW68KisBd/7TmavzPDlL2bpoDK5AjR2ZYrqU4TlGwM88GtQfvduBCAlSCj20X0w/4+rWMkseD9dAAkacjiyQ==} peerDependencies: '@capacitor/core': ^6.1.0 - '@capacitor/splash-screen@6.0.2': - resolution: {integrity: sha512-WC0KYZ+ev15up03xs4fTnoTKwBVUSxXsKKQr/8XAncvi/nAG8qrpanW8OlavSC5zF5e1IZZDLsI2GSv0SkZ7VQ==} + '@capacitor/splash-screen@6.0.3': + resolution: {integrity: sha512-tpVljeNGSwVCIc8lMQkyiCQFokk2PwgYPdDtPnGjFthqmXW/WhIxW8QYl4MUqyLwwgwTEbp4u3Kcv2zqQu2L6Q==} peerDependencies: '@capacitor/core': ^6.0.0 @@ -21542,9 +21579,9 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@capacitor/android@6.1.2(@capacitor/core@6.1.2)': + '@capacitor/android@6.1.2(@capacitor/core@6.2.0)': dependencies: - '@capacitor/core': 6.1.2 + '@capacitor/core': 6.2.0 '@capacitor/cli@6.1.2': dependencies: @@ -21569,17 +21606,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@capacitor/core@6.1.2': + '@capacitor/core@6.2.0': dependencies: tslib: 2.7.0 - '@capacitor/ios@6.1.2(@capacitor/core@6.1.2)': + '@capacitor/ios@6.1.2(@capacitor/core@6.2.0)': dependencies: - '@capacitor/core': 6.1.2 + '@capacitor/core': 6.2.0 - '@capacitor/splash-screen@6.0.2(@capacitor/core@6.1.2)': + '@capacitor/splash-screen@6.0.3(@capacitor/core@6.2.0)': dependencies: - '@capacitor/core': 6.1.2 + '@capacitor/core': 6.2.0 '@changesets/apply-release-plan@7.0.5': dependencies: @@ -24475,14 +24512,14 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 20.17.6 jest-mock: 29.7.0 '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.7.4 + '@types/node': 20.17.6 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -24518,7 +24555,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.7.4 + '@types/node': 20.17.6 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -29337,7 +29374,7 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 22.7.4 + '@types/node': 20.17.6 '@types/node@12.20.55': {} @@ -29869,14 +29906,14 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(msw@2.6.4(@types/node@20.16.10)(typescript@5.5.4))(vite@5.4.8(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1))': + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(msw@2.6.4(@types/node@20.16.10)(typescript@5.5.4))(vite@5.4.11(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1))': dependencies: '@vitest/spy': 2.1.2 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: msw: 2.6.4(@types/node@20.16.10)(typescript@5.5.4) - vite: 5.4.8(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1) + vite: 5.4.11(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1) '@vitest/mocker@2.1.4(msw@2.6.4(@types/node@20.17.6)(typescript@5.6.3))(vite@5.4.11(@types/node@20.17.6)(less@4.2.0)(sass@1.79.4)(terser@5.34.1))': dependencies: @@ -31556,7 +31593,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 22.7.4 + '@types/node': 20.17.6 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -35987,7 +36024,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 20.17.6 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -36008,7 +36045,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 20.17.6 jest-util: 29.7.0 jest-regex-util@27.5.1: {} @@ -36025,7 +36062,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 20.17.6 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -36042,13 +36079,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.7.4 + '@types/node': 20.17.6 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 22.7.4 + '@types/node': 20.17.6 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -43297,7 +43334,7 @@ snapshots: vitest@2.1.2(@types/node@20.16.10)(jsdom@24.1.3)(less@4.2.0)(msw@2.6.4(@types/node@20.16.10)(typescript@5.5.4))(sass@1.79.4)(terser@5.34.1): dependencies: '@vitest/expect': 2.1.2 - '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(msw@2.6.4(@types/node@20.16.10)(typescript@5.5.4))(vite@5.4.8(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1)) + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(msw@2.6.4(@types/node@20.16.10)(typescript@5.5.4))(vite@5.4.11(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1)) '@vitest/pretty-format': 2.1.2 '@vitest/runner': 2.1.2 '@vitest/snapshot': 2.1.2 @@ -43312,7 +43349,7 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.8(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1) + vite: 5.4.11(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1) vite-node: 2.1.2(@types/node@20.16.10)(less@4.2.0)(sass@1.79.4)(terser@5.34.1) why-is-node-running: 2.3.0 optionalDependencies: