diff --git a/components/charts/ExerciseProgressionChart.tsx b/components/charts/ExerciseProgressionChart.tsx index 32b50717..b432d931 100644 --- a/components/charts/ExerciseProgressionChart.tsx +++ b/components/charts/ExerciseProgressionChart.tsx @@ -260,12 +260,12 @@ export const ExerciseProgressionChart: React.FC< exercise.tracking_type === null || exercise.tracking_type === "weight" || exercise.tracking_type === "assisted"; - const baselineValue = - preRangeBaseline != null - ? isWeightTypePre - ? preRangeBaseline * conversionFactor - : preRangeBaseline - : undefined; + const rawBaseline = preRangeBaseline != null + ? isWeightTypePre + ? preRangeBaseline * conversionFactor + : preRangeBaseline + : undefined; + const baselineValue = rawBaseline != null && rawBaseline > 0 ? rawBaseline : undefined; for (let i = 0; i < buckets.length; i++) { if (buckets[i].value === null && baselineValue !== undefined) { buckets[i] = { ...buckets[i], value: baselineValue }; @@ -278,7 +278,7 @@ export const ExerciseProgressionChart: React.FC< prValue != null && prValue > 0 ? prValue * conversionFactor : undefined; return buckets.map((bucket) => { - const metric = bucket.value as number; + const metric = bucket.value ?? 0; const isPR = bucket.hasData && convertedPR != null && diff --git a/jestSetupFile.js b/jestSetupFile.js index e38acff9..8fca934d 100644 --- a/jestSetupFile.js +++ b/jestSetupFile.js @@ -77,7 +77,7 @@ jest.mock("expo-file-system/legacy", () => ({ documentDirectory: "/mock/document/directory/", getInfoAsync: jest.fn((path) => Promise.resolve({ - exists: path.includes("appData2.db"), + exists: path.includes("appData3.db"), isDirectory: false, }), ), diff --git a/utils/__tests__/initAppDataDB.test.ts b/utils/__tests__/initAppDataDB.test.ts index fb920e5a..23dcad0c 100644 --- a/utils/__tests__/initAppDataDB.test.ts +++ b/utils/__tests__/initAppDataDB.test.ts @@ -12,8 +12,9 @@ const mockDatabase = { describe("initializeAppData", () => { let mockDirCreate: jest.Mock; - let mockDbFileCopy: jest.Mock; - let mockOldDbFileDelete: jest.Mock; + let mockDbFileDelete: jest.Mock; + let mockOldDb1FileDelete: jest.Mock; + let mockOldDb2FileDelete: jest.Mock; let mockAssetFileCopy: jest.Mock; beforeEach(() => { @@ -21,24 +22,34 @@ describe("initializeAppData", () => { (openDatabase as jest.Mock).mockReturnValue(mockDatabase); mockDirCreate = jest.fn(); - mockDbFileCopy = jest.fn(); - mockOldDbFileDelete = jest.fn(); + mockDbFileDelete = jest.fn(); + mockOldDb1FileDelete = jest.fn(); + mockOldDb2FileDelete = jest.fn(); mockAssetFileCopy = jest.fn(); MockDirectory.mockImplementation(() => ({ create: mockDirCreate })); }); - const setupFileMocks = (dbExists: boolean, oldDbExists: boolean) => { + const setupFileMocks = ( + dbExists: boolean, + oldDb1Exists: boolean, + oldDb2Exists: boolean, + ) => { MockFile.mockImplementationOnce(() => ({ exists: dbExists, - copy: mockDbFileCopy, - uri: "/mock/document/directory/SQLite/appData2.db", + delete: mockDbFileDelete, + uri: "/mock/document/directory/SQLite/appData3.db", })); // dbFile MockFile.mockImplementationOnce(() => ({ - exists: oldDbExists, - delete: mockOldDbFileDelete, + exists: oldDb1Exists, + delete: mockOldDb1FileDelete, uri: "/mock/document/directory/SQLite/appData1.db", - })); // oldDbFile + })); // oldDbFile1 + MockFile.mockImplementationOnce(() => ({ + exists: oldDb2Exists, + delete: mockOldDb2FileDelete, + uri: "/mock/document/directory/SQLite/appData2.db", + })); // oldDbFile2 MockFile.mockImplementation(() => ({ exists: true, copy: mockAssetFileCopy, @@ -47,7 +58,7 @@ describe("initializeAppData", () => { }; it("should not copy database if it exists and dataVersion is sufficient", async () => { - setupFileMocks(true, false); + setupFileMocks(true, false, false); mockDatabase.getFirstAsync.mockResolvedValue({ value: "2.0" }); await initializeAppData(); @@ -57,11 +68,12 @@ describe("initializeAppData", () => { idempotent: true, }); expect(mockAssetFileCopy).not.toHaveBeenCalled(); - expect(mockOldDbFileDelete).not.toHaveBeenCalled(); + expect(mockOldDb1FileDelete).not.toHaveBeenCalled(); + expect(mockOldDb2FileDelete).not.toHaveBeenCalled(); }); it("should copy database if it does not exist", async () => { - setupFileMocks(false, false); + setupFileMocks(false, false, false); mockDatabase.getFirstAsync.mockResolvedValue({ value: "2.0" }); await initializeAppData(); @@ -69,14 +81,15 @@ describe("initializeAppData", () => { expect(Asset.fromModule).toHaveBeenCalled(); expect(mockAssetFileCopy).toHaveBeenCalledWith( expect.objectContaining({ - uri: "/mock/document/directory/SQLite/appData2.db", + uri: "/mock/document/directory/SQLite/appData3.db", }), ); - expect(mockOldDbFileDelete).not.toHaveBeenCalled(); + expect(mockOldDb1FileDelete).not.toHaveBeenCalled(); + expect(mockOldDb2FileDelete).not.toHaveBeenCalled(); }); it("should copy database if dataVersion is outdated", async () => { - setupFileMocks(true, false); + setupFileMocks(true, false, false); mockDatabase.getFirstAsync.mockResolvedValue({ value: "1.5" }); await initializeAppData(); @@ -85,18 +98,19 @@ describe("initializeAppData", () => { expect(mockAssetFileCopy).toHaveBeenCalled(); }); - it("should delete old database if it exists", async () => { - setupFileMocks(true, true); + it("should delete old database files if they exist", async () => { + setupFileMocks(true, true, true); mockDatabase.getFirstAsync.mockResolvedValue({ value: "2.0" }); await initializeAppData(); expect(mockAssetFileCopy).not.toHaveBeenCalled(); - expect(mockOldDbFileDelete).toHaveBeenCalled(); + expect(mockOldDb1FileDelete).toHaveBeenCalled(); + expect(mockOldDb2FileDelete).toHaveBeenCalled(); }); it("should proceed if dataVersion retrieval fails", async () => { - setupFileMocks(false, false); + setupFileMocks(false, false, false); mockDatabase.getFirstAsync.mockRejectedValue( new Error("Table does not exist"), ); @@ -105,6 +119,7 @@ describe("initializeAppData", () => { expect(Asset.fromModule).toHaveBeenCalled(); expect(mockAssetFileCopy).toHaveBeenCalled(); - expect(mockOldDbFileDelete).not.toHaveBeenCalled(); + expect(mockOldDb1FileDelete).not.toHaveBeenCalled(); + expect(mockOldDb2FileDelete).not.toHaveBeenCalled(); }); }); diff --git a/utils/database.ts b/utils/database.ts index 0a840e32..6c7ac624 100644 --- a/utils/database.ts +++ b/utils/database.ts @@ -114,7 +114,7 @@ export const updateAppExerciseIds = async (): Promise => { }; export const copyDataFromAppDataToUserData = async (): Promise => { - const appDataDB = await openDatabase("appData2.db"); + const appDataDB = await openDatabase("appData3.db"); const userDataDB = await openDatabase("userData.db"); interface ExerciseCheckResult { @@ -306,9 +306,9 @@ export const syncExerciseFlagsFromAppData = async (): Promise => { const versionResult = await userDataDB.getFirstAsync( "SELECT value FROM settings WHERE key = 'dataVersion'", ); - if (Number(versionResult?.value) >= 1.9) return; + if (Number(versionResult?.value) >= 2.0) return; - const appDataDB = await openDatabase("appData2.db"); + const appDataDB = await openDatabase("appData3.db"); const appExercises = await appDataDB.getAllAsync<{ exercise_id: number; is_unilateral: number; @@ -325,7 +325,7 @@ export const syncExerciseFlagsFromAppData = async (): Promise => { } await userDataDB.runAsync( "INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", - ["dataVersion", "1.9"], + ["dataVersion", "2.0"], ); await userDataDB.execAsync("COMMIT"); } catch (err) { diff --git a/utils/initAppDataDB.ts b/utils/initAppDataDB.ts index 17c43a80..d5871fd7 100644 --- a/utils/initAppDataDB.ts +++ b/utils/initAppDataDB.ts @@ -2,12 +2,13 @@ import { File, Directory, Paths } from "expo-file-system"; import { Asset } from "expo-asset"; import { openDatabase } from "./database"; -const DATABASE_NAME = "appData2.db"; +const DATABASE_NAME = "appData3.db"; const copyDatabase = async (): Promise => { const dbFolder = new Directory(Paths.document, "SQLite"); const dbFile = new File(Paths.document, "SQLite", DATABASE_NAME); - const oldDbFile = new File(Paths.document, "SQLite", "appData1.db"); + const oldDbFile1 = new File(Paths.document, "SQLite", "appData1.db"); + const oldDbFile2 = new File(Paths.document, "SQLite", "appData2.db"); const userDataDB = await openDatabase("userData.db"); dbFolder.create({ intermediates: true, idempotent: true }); @@ -27,17 +28,33 @@ const copyDatabase = async (): Promise => { ); } - if (!dbFile.exists || dataVersion === null || dataVersion < 1.9) { - console.log("Copying appData2.db ..."); + if (!dbFile.exists || dataVersion === null || dataVersion < 2.0) { + console.log(`Copying ${DATABASE_NAME} ...`); + const tempFile = new File(Paths.document, "SQLite", `${DATABASE_NAME}.tmp`); + if (tempFile.exists) { + tempFile.delete(); + } // eslint-disable-next-line @typescript-eslint/no-require-imports const asset = Asset.fromModule(require(`../assets/db/${DATABASE_NAME}`)); await asset.downloadAsync(); - new File(asset.localUri!).copy(dbFile); + new File(asset.localUri!).copy(tempFile); + if (!tempFile.exists) { + throw new Error(`Failed to stage ${DATABASE_NAME} to temp file`); + } + if (dbFile.exists) { + dbFile.delete(); + } + tempFile.move(dbFile); } - if (oldDbFile.exists) { - console.log("Removing outdated appData1.db ..."); - oldDbFile.delete(); + if (oldDbFile1.exists) { + console.log(`Removing outdated ${oldDbFile1.name} ...`); + oldDbFile1.delete(); + } + + if (oldDbFile2.exists) { + console.log(`Removing outdated ${oldDbFile2.name} ...`); + oldDbFile2.delete(); } };