fix: mobile edit handling (#25315)

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
This commit is contained in:
Brandon Wees
2026-01-19 12:22:53 -06:00
committed by GitHub
parent b3f5b8ede8
commit 1b56bb84f9
64 changed files with 9350 additions and 168 deletions

View File

@@ -86,7 +86,7 @@ export const assetStub = {
make: 'FUJIFILM',
model: 'X-T50',
lensModel: 'XF27mm F2.8 R WR',
editCount: 0,
isEdited: false,
...asset,
}),
noResizePath: Object.freeze({
@@ -126,7 +126,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
noWebpPath: Object.freeze({
@@ -168,7 +168,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
noThumbhash: Object.freeze({
@@ -207,7 +207,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
primaryImage: Object.freeze({
@@ -256,7 +256,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
image: Object.freeze({
@@ -303,7 +303,7 @@ export const assetStub = {
width: null,
visibility: AssetVisibility.Timeline,
edits: [],
editCount: 0,
isEdited: false,
}),
trashed: Object.freeze({
@@ -347,7 +347,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
trashedOffline: Object.freeze({
@@ -391,7 +391,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
archived: Object.freeze({
id: 'asset-id',
@@ -434,7 +434,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
external: Object.freeze({
@@ -477,7 +477,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
image1: Object.freeze({
@@ -520,7 +520,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
imageFrom2015: Object.freeze({
@@ -562,7 +562,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
video: Object.freeze({
@@ -606,7 +606,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
livePhotoMotionAsset: Object.freeze({
@@ -627,7 +627,7 @@ export const assetStub = {
width: null,
height: null,
edits: [] as AssetEditActionItem[],
editCount: 0,
isEdited: false,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; exifInfo: Exif; edits: AssetEditActionItem[] }),
livePhotoStillAsset: Object.freeze({
@@ -649,7 +649,7 @@ export const assetStub = {
width: null,
height: null,
edits: [] as AssetEditActionItem[],
editCount: 0,
isEdited: false,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }),
livePhotoWithOriginalFileName: Object.freeze({
@@ -673,7 +673,7 @@ export const assetStub = {
width: null,
height: null,
edits: [] as AssetEditActionItem[],
editCount: 0,
isEdited: false,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }),
withLocation: Object.freeze({
@@ -721,7 +721,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
sidecar: Object.freeze({
@@ -760,7 +760,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
sidecarWithoutExt: Object.freeze({
@@ -796,7 +796,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
hasEncodedVideo: Object.freeze({
@@ -839,7 +839,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
hasFileExtension: Object.freeze({
@@ -879,7 +879,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
imageDng: Object.freeze({
@@ -923,7 +923,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
imageHif: Object.freeze({
@@ -967,7 +967,7 @@ export const assetStub = {
width: null,
height: null,
edits: [],
editCount: 0,
isEdited: false,
}),
panoramaTif: Object.freeze({
@@ -1068,7 +1068,7 @@ export const assetStub = {
},
},
] as AssetEditActionItem[],
editCount: 1,
isEdited: true,
}),
withoutEdits: Object.freeze({
@@ -1116,6 +1116,6 @@ export const assetStub = {
width: 2160,
visibility: AssetVisibility.Timeline,
edits: [],
editCount: 0,
isEdited: false,
}),
};

View File

@@ -159,7 +159,7 @@ export const sharedLinkStub = {
visibility: AssetVisibility.Timeline,
width: 500,
height: 500,
editCount: 0,
isEdited: false,
},
],
albumId: null,

View File

@@ -537,7 +537,7 @@ const assetInsert = (asset: Partial<Insertable<AssetTable>> = {}) => {
fileModifiedAt: now,
localDateTime: now,
visibility: AssetVisibility.Timeline,
editCount: 0,
isEdited: false,
};
return {

View File

@@ -24,32 +24,32 @@ beforeAll(async () => {
describe(AssetEditRepository.name, () => {
describe('replaceAll', () => {
it('should increment editCount on insert', async () => {
it('should set isEdited on insert', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
]);
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 1 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: true });
});
it('should increment editCount when inserting multiple edits', async () => {
it('should set isEdited when inserting multiple edits', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
@@ -58,18 +58,18 @@ describe(AssetEditRepository.name, () => {
]);
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 3 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: true });
});
it('should decrement editCount', async () => {
it('should keep isEdited when removing some edits', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
@@ -77,23 +77,27 @@ describe(AssetEditRepository.name, () => {
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
]);
await expect(
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: true });
await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
]);
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 1 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: true });
});
it('should set editCount to 0 if all edits are deleted', async () => {
it('should set isEdited to false if all edits are deleted', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
@@ -104,8 +108,8 @@ describe(AssetEditRepository.name, () => {
await sut.replaceAll(asset.id, []);
await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 });
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: false });
});
});
});

View File

@@ -83,7 +83,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
libraryId: asset.libraryId,
width: asset.width,
height: asset.height,
editCount: asset.editCount,
isEdited: asset.isEdited,
},
type: SyncEntityType.AlbumAssetCreateV1,
},

View File

@@ -64,7 +64,7 @@ describe(SyncEntityType.AssetV1, () => {
libraryId: asset.libraryId,
width: asset.width,
height: asset.height,
editCount: asset.editCount,
isEdited: asset.isEdited,
},
type: 'AssetV1',
},

View File

@@ -63,7 +63,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
type: asset.type,
visibility: asset.visibility,
duration: asset.duration,
editCount: asset.editCount,
isEdited: asset.isEdited,
stackId: null,
livePhotoVideoId: null,
libraryId: asset.libraryId,

View File

@@ -253,7 +253,7 @@ const assetFactory = (asset: Partial<MapAsset> = {}) => ({
visibility: AssetVisibility.Timeline,
width: null,
height: null,
editCount: 0,
isEdited: false,
...asset,
});