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:
renovate[bot]
2026-10-28 20:41:28 +03:00
committed by Codex
parent 3127dbec71
commit 7fb7699c06
141 changed files with 654 additions and 761 deletions
+1 -1
View File
@@ -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);
+30 -34
View File
@@ -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
+19 -30
View File
@@ -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
View File
@@ -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);