chore(deps): update dependency eslint-plugin-unicorn to v78 (#32588)
Co-authored-by: Jason Rasmussen <jason@rasm.me> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev> (cherry picked from commit 72e2bc8b0bc4a7e0a84ac16c0bde7f5e538729fb)
This commit is contained in:
+1
-1
@@ -40,7 +40,7 @@
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^75.0.0",
|
||||
"eslint-plugin-unicorn": "^78.0.0",
|
||||
"exiftool-vendored": "^39.0.0",
|
||||
"globals": "^17.0.0",
|
||||
"luxon": "^3.4.4",
|
||||
|
||||
@@ -14,9 +14,7 @@ type DayDistributionStrategy = (
|
||||
rng: SeededRandom,
|
||||
) => MockTimelineAsset[];
|
||||
|
||||
/**
|
||||
* Strategies for determining total asset count per month
|
||||
*/
|
||||
/** Strategies for determining total asset count per month */
|
||||
export const ASSET_DISTRIBUTION: Record<MonthDistribution, AssetDistributionStrategy | null> = {
|
||||
empty: null, // Special case - handled separately
|
||||
sparse: (rng) => rng.nextInt(3, 9), // 3-8 assets
|
||||
@@ -25,9 +23,7 @@ export const ASSET_DISTRIBUTION: Record<MonthDistribution, AssetDistributionStra
|
||||
'very-dense': (rng) => rng.nextInt(80, 151), // 80-150 assets
|
||||
};
|
||||
|
||||
/**
|
||||
* Strategies for distributing assets across days within a month
|
||||
*/
|
||||
/** Strategies for distributing assets across days within a month */
|
||||
export const DAY_DISTRIBUTION: Record<DayPattern, DayDistributionStrategy> = {
|
||||
'single-day': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// All assets on one day in the middle of the month
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* Generator functions for timeline model objects
|
||||
*/
|
||||
/** Generator functions for timeline model objects */
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
@@ -12,9 +10,7 @@ import { ASSET_DISTRIBUTION, DAY_DISTRIBUTION } from './distribution-patterns';
|
||||
import type { MockTimelineAsset, MockTimelineData, SerializedTimelineData, TimelineConfig } from './timeline-config';
|
||||
import { ASPECT_RATIO_WEIGHTS, GENERATION_CONSTANTS, validateTimelineConfig } from './timeline-config';
|
||||
|
||||
/**
|
||||
* Generate a random aspect ratio based on weighted probabilities
|
||||
*/
|
||||
/** Generate a random aspect ratio based on weighted probabilities */
|
||||
export function generateAspectRatio(rng: SeededRandom): string {
|
||||
const random = rng.next();
|
||||
let cumulative = 0;
|
||||
@@ -89,9 +85,7 @@ export function generateAsset(
|
||||
return asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate assets for a specific day
|
||||
*/
|
||||
/** Generate assets for a specific day */
|
||||
export function generateDayAssets(
|
||||
year: number,
|
||||
month: number,
|
||||
@@ -104,10 +98,10 @@ export function generateDayAssets(
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute assets evenly across consecutive days
|
||||
*
|
||||
* @returns Array of generated timeline assets
|
||||
*/
|
||||
Distribute assets evenly across consecutive days
|
||||
|
||||
@returns Array of generated timeline assets
|
||||
*/
|
||||
export function generateConsecutiveDays(
|
||||
year: number,
|
||||
month: number,
|
||||
@@ -133,9 +127,7 @@ export function generateConsecutiveDays(
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate assets for a month with specified distribution pattern
|
||||
*/
|
||||
/** Generate assets for a month with specified distribution pattern */
|
||||
export function generateMonthAssets(
|
||||
year: number,
|
||||
month: number,
|
||||
@@ -173,9 +165,7 @@ export function generateMonthAssets(
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main generator function for timeline data
|
||||
*/
|
||||
/** Main generator function for timeline data */
|
||||
export function generateTimelineData(config: TimelineConfig): MockTimelineData {
|
||||
validateTimelineConfig(config);
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* REST API output functions for converting timeline data to API response formats
|
||||
*/
|
||||
/** REST API output functions for converting timeline data to API response formats */
|
||||
|
||||
import {
|
||||
AlbumUserRole,
|
||||
@@ -19,9 +17,7 @@ import { signupDto } from 'src/fixtures.js';
|
||||
import { parseTimeBucketKey } from 'src/ui/generators/timeline/utils.js';
|
||||
import type { MockTimelineAsset, MockTimelineData } from './timeline-config';
|
||||
|
||||
/**
|
||||
* Convert timeline/asset models to columnar format (parallel arrays)
|
||||
*/
|
||||
/** Convert timeline/asset models to columnar format (parallel arrays) */
|
||||
export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetResponseDto {
|
||||
const result: TimeBucketAssetResponseDto = {
|
||||
id: [],
|
||||
@@ -70,11 +66,11 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a single bucket from timeline data (mimics getTimeBucket API)
|
||||
* Automatically handles both ISO timestamp and simple month formats
|
||||
* Returns data in columnar format matching the actual API
|
||||
* When albumId is provided, only returns assets from that album
|
||||
*/
|
||||
Extract a single bucket from timeline data (mimics getTimeBucket API)
|
||||
Automatically handles both ISO timestamp and simple month formats
|
||||
Returns data in columnar format matching the actual API
|
||||
When albumId is provided, only returns assets from that album
|
||||
*/
|
||||
export function getTimeBucket(
|
||||
timelineData: MockTimelineData,
|
||||
timeBucket: string,
|
||||
@@ -141,16 +137,16 @@ export type Changes = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to determine if an asset should be included based on filter criteria
|
||||
* @param asset - The asset to check
|
||||
* @param isTrashed - Filter for trashed status (undefined means no filter)
|
||||
* @param isArchived - Filter for archived status (undefined means no filter)
|
||||
* @param isFavorite - Filter for favorite status (undefined means no filter)
|
||||
* @param deletedAssetIds - Set of IDs for assets that have been deleted
|
||||
* @param archivedAssetIds - Set of IDs for assets that have been archived
|
||||
* @param favoritedAssetIds - Set of IDs for assets that have been favorited
|
||||
* @returns true if the asset matches all filter criteria
|
||||
*/
|
||||
Helper function to determine if an asset should be included based on filter criteria
|
||||
@param asset - The asset to check
|
||||
@param isTrashed - Filter for trashed status (undefined means no filter)
|
||||
@param isArchived - Filter for archived status (undefined means no filter)
|
||||
@param isFavorite - Filter for favorite status (undefined means no filter)
|
||||
@param deletedAssetIds - Set of IDs for assets that have been deleted
|
||||
@param archivedAssetIds - Set of IDs for assets that have been archived
|
||||
@param favoritedAssetIds - Set of IDs for assets that have been favorited
|
||||
@returns true if the asset matches all filter criteria
|
||||
*/
|
||||
function shouldIncludeAsset(
|
||||
asset: MockTimelineAsset,
|
||||
isTrashed: boolean | undefined,
|
||||
@@ -175,9 +171,9 @@ function shouldIncludeAsset(
|
||||
return isFavorite === undefined || actuallyFavorited === isFavorite;
|
||||
}
|
||||
/**
|
||||
* Get summary for all buckets (mimics getTimeBuckets API)
|
||||
* When albumId is provided, only includes buckets that contain assets from that album
|
||||
*/
|
||||
Get summary for all buckets (mimics getTimeBuckets API)
|
||||
When albumId is provided, only includes buckets that contain assets from that album
|
||||
*/
|
||||
export function getTimeBuckets(
|
||||
timelineData: MockTimelineData,
|
||||
isTrashed: boolean | undefined,
|
||||
@@ -279,9 +275,9 @@ const createDefaultOwner = (ownerId: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a TimelineAssetConfig to a full AssetResponseDto
|
||||
* This matches the response from GET /api/assets/:id
|
||||
*/
|
||||
Convert a TimelineAssetConfig to a full AssetResponseDto
|
||||
This matches the response from GET /api/assets/:id
|
||||
*/
|
||||
export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserResponseDto): AssetResponseDto {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
@@ -292,7 +288,7 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
||||
make: null,
|
||||
model: null,
|
||||
exifImageWidth: asset.ratio > 1 ? 4000 : 3000,
|
||||
exifImageHeight: asset.ratio > 1 ? Math.round(4000 / asset.ratio) : Math.round(3000 * asset.ratio),
|
||||
exifImageHeight: Math.round(asset.ratio > 1 ? 4000 : 3000 / asset.ratio),
|
||||
fileSizeInByte: asset.fileSizeInByte,
|
||||
orientation: '1',
|
||||
dateTimeOriginal: asset.fileCreatedAt,
|
||||
@@ -348,9 +344,9 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single asset by ID from timeline data
|
||||
* This matches the response from GET /api/assets/:id
|
||||
*/
|
||||
Get a single asset by ID from timeline data
|
||||
This matches the response from GET /api/assets/:id
|
||||
*/
|
||||
export function getAsset(
|
||||
timelineData: MockTimelineData,
|
||||
assetId: string,
|
||||
@@ -368,9 +364,9 @@ export function getAsset(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mock album from timeline data
|
||||
* This matches the response from GET /api/albums/:id
|
||||
*/
|
||||
Get a mock album from timeline data
|
||||
This matches the response from GET /api/albums/:id
|
||||
*/
|
||||
export function getAlbum(
|
||||
timelineData: MockTimelineData,
|
||||
ownerId: string,
|
||||
|
||||
@@ -57,9 +57,7 @@ export type MonthSpec = {
|
||||
pattern: DayPattern;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration for timeline data generation
|
||||
*/
|
||||
/** Configuration for timeline data generation */
|
||||
export type TimelineConfig = {
|
||||
ownerId?: string;
|
||||
months: MonthSpec[];
|
||||
@@ -88,9 +86,7 @@ export type SerializedTimelineData = {
|
||||
album: MockAlbum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a TimelineConfig object to ensure all values are within expected ranges
|
||||
*/
|
||||
/** Validates a TimelineConfig object to ensure all values are within expected ranges */
|
||||
export function validateTimelineConfig(config: TimelineConfig): void {
|
||||
if (!config.months || config.months.length === 0) {
|
||||
throw new Error('TimelineConfig must contain at least one month');
|
||||
@@ -146,9 +142,7 @@ export function validateTimelineConfig(config: TimelineConfig): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default timeline configuration
|
||||
*/
|
||||
/** Create a default timeline configuration */
|
||||
export function createDefaultTimelineConfig(): TimelineConfig {
|
||||
const months: MonthSpec[] = [
|
||||
// 2024 - Mix of patterns
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { GENERATION_CONSTANTS, MockTimelineAsset } from 'src/ui/generators/timeline/timeline-config.js';
|
||||
|
||||
/**
|
||||
* Linear Congruential Generator for deterministic pseudo-random numbers
|
||||
*/
|
||||
/** Linear Congruential Generator for deterministic pseudo-random numbers */
|
||||
export class SeededRandom {
|
||||
private seed: number;
|
||||
|
||||
@@ -11,38 +9,32 @@ export class SeededRandom {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate next random number in range [0, 1)
|
||||
*/
|
||||
/** Generate next random number in range [0, 1) */
|
||||
next(): number {
|
||||
// LCG parameters from Numerical Recipes
|
||||
this.seed = (this.seed * 1_664_525 + 1_013_904_223) % 2_147_483_647;
|
||||
return this.seed / 2_147_483_647;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random integer in range [min, max)
|
||||
*/
|
||||
/** Generate random integer in range [min, max) */
|
||||
nextInt(min: number, max: number): number {
|
||||
return Math.floor(this.next() * (max - min)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random boolean with given probability
|
||||
*/
|
||||
/** Generate random boolean with given probability */
|
||||
nextBoolean(probability = 0.5): boolean {
|
||||
return this.next() < probability;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select random days using seed variation to avoid collisions.
|
||||
*
|
||||
* @param daysInMonth - Total number of days in the month
|
||||
* @param numDays - Number of days to select
|
||||
* @param rng - Random number generator instance
|
||||
* @returns Array of selected day numbers, sorted in descending order
|
||||
*/
|
||||
Select random days using seed variation to avoid collisions.
|
||||
|
||||
@param daysInMonth - Total number of days in the month
|
||||
@param numDays - Number of days to select
|
||||
@param rng - Random number generator instance
|
||||
@returns Array of selected day numbers, sorted in descending order
|
||||
*/
|
||||
export function selectRandomDays(daysInMonth: number, numDays: number, rng: SeededRandom): number[] {
|
||||
const selectedDays = new Set<number>();
|
||||
const maxAttempts = numDays * GENERATION_CONSTANTS.MAX_SELECT_ATTEMPTS; // Safety limit
|
||||
@@ -64,9 +56,7 @@ export function selectRandomDays(daysInMonth: number, numDays: number, rng: Seed
|
||||
return [...selectedDays].toSorted((a, b) => b - a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select item from array using seeded random
|
||||
*/
|
||||
/** Select item from array using seeded random */
|
||||
export function selectRandom<T>(arr: T[], rng: SeededRandom): T {
|
||||
if (arr.length === 0) {
|
||||
throw new Error('Cannot select from empty array');
|
||||
@@ -75,9 +65,7 @@ export function selectRandom<T>(arr: T[], rng: SeededRandom): T {
|
||||
return arr[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Select multiple random items from array using seeded random without duplicates
|
||||
*/
|
||||
/** Select multiple random items from array using seeded random without duplicates */
|
||||
export function selectRandomMultiple<T>(arr: T[], count: number, rng: SeededRandom): T[] {
|
||||
if (arr.length === 0) {
|
||||
throw new Error('Cannot select from empty array');
|
||||
@@ -104,11 +92,11 @@ export function selectRandomMultiple<T>(arr: T[], count: number, rng: SeededRand
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse timeBucket parameter to extract year-month key
|
||||
* Handles both formats:
|
||||
* - ISO timestamp: "2024-12-01T00:00:00.000Z" -> "2024-12-01"
|
||||
* - Simple format: "2024-12-01" -> "2024-12-01"
|
||||
*/
|
||||
Parse timeBucket parameter to extract year-month key
|
||||
Handles both formats:
|
||||
- ISO timestamp: "2024-12-01T00:00:00.000Z" -> "2024-12-01"
|
||||
- Simple format: "2024-12-01" -> "2024-12-01"
|
||||
*/
|
||||
export function parseTimeBucketKey(timeBucket: string): string {
|
||||
if (!timeBucket) {
|
||||
throw new Error('timeBucket parameter cannot be empty');
|
||||
@@ -150,6 +138,7 @@ export function getMockAsset(
|
||||
return null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unicorn/no-unnecessary-parameters
|
||||
const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => {
|
||||
if (unit === 'day') {
|
||||
return !date1.startOf('day').equals(date2.startOf('day'));
|
||||
|
||||
@@ -242,6 +242,7 @@ test.describe('OCR text interaction', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unicorn/no-unnecessary-parameters
|
||||
const createTouch = (id: number, x: number, y: number) => {
|
||||
return new Touch({
|
||||
identifier: id,
|
||||
|
||||
@@ -150,10 +150,10 @@ test.describe('Timeline', () => {
|
||||
});
|
||||
test.describe('keyboard', () => {
|
||||
/**
|
||||
* This text tests keyboard nativation, and also ensures that the scroll-to-asset behavior
|
||||
* scrolls the minimum amount. That is, if you are navigating using right arrow (auto scrolling
|
||||
* as necessary downwards), then the asset should always be at the lowest row of the grid.
|
||||
*/
|
||||
This text tests keyboard nativation, and also ensures that the scroll-to-asset behavior
|
||||
scrolls the minimum amount. That is, if you are navigating using right arrow (auto scrolling
|
||||
as necessary downwards), then the asset should always be at the lowest row of the grid.
|
||||
*/
|
||||
test('Next/previous asset - ArrowRight/ArrowLeft', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await thumbnailUtils.withAssetId(page, assets[0].id).focus();
|
||||
|
||||
+5
-5
@@ -270,13 +270,13 @@ export const utils = {
|
||||
}
|
||||
},
|
||||
|
||||
waitForWebsocketEvent: ({ event, id, total: count, timeout: ms }: WaitOptions): Promise<void> => {
|
||||
waitForWebsocketEvent: ({ event, id, total: count, timeout: ms = 10_000 }: WaitOptions): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!id && !count) {
|
||||
reject(new Error('id or count must be provided for waitForWebsocketEvent'));
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => reject(new Error(`Timed out waiting for ${event} event`)), ms || 10_000);
|
||||
const timeout = setTimeout(() => reject(new Error(`Timed out waiting for ${event} event`)), ms);
|
||||
const type = id ? `id=${id}` : `count=${count}`;
|
||||
console.log(`Waiting for ${event} [${type}]`);
|
||||
const set = events[event];
|
||||
@@ -550,7 +550,7 @@ export const utils = {
|
||||
{
|
||||
headers: asBearerAuth(accessToken),
|
||||
fetch: (...args: Parameters<typeof fetch>) =>
|
||||
// eslint-disable-next-line unicorn/no-invalid-argument-count, unicorn/prefer-await
|
||||
// eslint-disable-next-line unicorn/prefer-await
|
||||
fetch(...args).then((response) => {
|
||||
setCookie = response.headers.getSetCookie();
|
||||
return response;
|
||||
@@ -660,10 +660,10 @@ export const utils = {
|
||||
return !jobCounts.active && (!jobCounts.waiting || queues[queue].queueStatus.isPaused);
|
||||
},
|
||||
|
||||
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseLegacyDto, ms?: number) => {
|
||||
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseLegacyDto, ms: number = 15_000) => {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 15_000);
|
||||
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms);
|
||||
|
||||
while (true) {
|
||||
const done = await utils.isQueueEmpty(accessToken, queue);
|
||||
|
||||
Reference in New Issue
Block a user